feat: add levee work records
This commit is contained in:
142
backend/apps/levee_work/serializers.py
Normal file
142
backend/apps/levee_work/serializers.py
Normal file
@@ -0,0 +1,142 @@
|
||||
from django.db import transaction
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.plans.models import Plan
|
||||
from apps.workrecords.services import sync_levee_work_record
|
||||
from .models import LeveeWorkSession, LeveeWorkSessionItem
|
||||
|
||||
|
||||
class LeveeWorkSessionItemReadSerializer(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,
|
||||
)
|
||||
group_name = serializers.CharField(source='field.group_name', read_only=True, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = LeveeWorkSessionItem
|
||||
fields = [
|
||||
'id',
|
||||
'field',
|
||||
'field_name',
|
||||
'field_area_tan',
|
||||
'group_name',
|
||||
'plan',
|
||||
'crop_name_snapshot',
|
||||
'variety_name_snapshot',
|
||||
]
|
||||
|
||||
|
||||
class LeveeWorkSessionSerializer(serializers.ModelSerializer):
|
||||
items = LeveeWorkSessionItemReadSerializer(many=True, read_only=True)
|
||||
work_record_id = serializers.IntegerField(source='work_record.id', read_only=True)
|
||||
item_count = serializers.SerializerMethodField()
|
||||
|
||||
class Meta:
|
||||
model = LeveeWorkSession
|
||||
fields = [
|
||||
'id',
|
||||
'year',
|
||||
'date',
|
||||
'title',
|
||||
'notes',
|
||||
'work_record_id',
|
||||
'item_count',
|
||||
'items',
|
||||
'created_at',
|
||||
'updated_at',
|
||||
]
|
||||
|
||||
def get_item_count(self, obj):
|
||||
return len(obj.items.all())
|
||||
|
||||
|
||||
class LeveeWorkSessionItemWriteInputSerializer(serializers.Serializer):
|
||||
field = serializers.IntegerField()
|
||||
plan = serializers.IntegerField(required=False, allow_null=True)
|
||||
|
||||
|
||||
class LeveeWorkSessionWriteSerializer(serializers.ModelSerializer):
|
||||
items = LeveeWorkSessionItemWriteInputSerializer(many=True, write_only=True)
|
||||
|
||||
class Meta:
|
||||
model = LeveeWorkSession
|
||||
fields = ['id', 'year', 'date', 'title', 'notes', 'items']
|
||||
|
||||
def validate(self, attrs):
|
||||
year = attrs.get('year', getattr(self.instance, 'year', None))
|
||||
date = attrs.get('date', getattr(self.instance, 'date', None))
|
||||
if year is not None and date is not None and year != date.year:
|
||||
raise serializers.ValidationError({'year': 'year は date.year と一致させてください。'})
|
||||
return attrs
|
||||
|
||||
def validate_items(self, value):
|
||||
if not value:
|
||||
raise serializers.ValidationError('items を1件以上指定してください。')
|
||||
seen = set()
|
||||
for item in value:
|
||||
key = item['field']
|
||||
if key in seen:
|
||||
raise serializers.ValidationError('同一 session 内で同じ圃場を重複登録できません。')
|
||||
seen.add(key)
|
||||
return value
|
||||
|
||||
@transaction.atomic
|
||||
def create(self, validated_data):
|
||||
items_data = validated_data.pop('items', [])
|
||||
validated_data['title'] = (validated_data.get('title') or '').strip() or '水稲畔塗'
|
||||
session = LeveeWorkSession.objects.create(**validated_data)
|
||||
self._replace_items(session, items_data)
|
||||
sync_levee_work_record(session)
|
||||
return session
|
||||
|
||||
@transaction.atomic
|
||||
def update(self, instance, validated_data):
|
||||
items_data = validated_data.pop('items', None)
|
||||
for attr, value in validated_data.items():
|
||||
if attr == 'title':
|
||||
value = (value or '').strip() or '水稲畔塗'
|
||||
setattr(instance, attr, value)
|
||||
if 'title' not in validated_data:
|
||||
instance.title = (instance.title or '').strip() or '水稲畔塗'
|
||||
instance.save()
|
||||
if items_data is not None:
|
||||
self._replace_items(instance, items_data)
|
||||
sync_levee_work_record(instance)
|
||||
return instance
|
||||
|
||||
def _replace_items(self, session, items_data):
|
||||
session.items.all().delete()
|
||||
for item in items_data:
|
||||
plan = self._resolve_plan(session.year, item['field'], item.get('plan'))
|
||||
LeveeWorkSessionItem.objects.create(
|
||||
session=session,
|
||||
field_id=item['field'],
|
||||
plan=plan,
|
||||
crop_name_snapshot=plan.crop.name,
|
||||
variety_name_snapshot=plan.variety.name if plan.variety else '',
|
||||
)
|
||||
|
||||
def _resolve_plan(self, year, field_id, plan_id):
|
||||
queryset = Plan.objects.select_related('crop', 'variety').filter(
|
||||
year=year,
|
||||
field_id=field_id,
|
||||
crop__name='水稲',
|
||||
)
|
||||
if plan_id is not None:
|
||||
try:
|
||||
return queryset.get(id=plan_id)
|
||||
except Plan.DoesNotExist as exc:
|
||||
raise serializers.ValidationError(
|
||||
{'items': f'field={field_id} に対応する水稲作付け計画(plan={plan_id})が見つかりません。'}
|
||||
) from exc
|
||||
|
||||
plan = queryset.first()
|
||||
if plan is None:
|
||||
raise serializers.ValidationError(
|
||||
{'items': f'field={field_id} は当年の水稲作付け圃場ではありません。'}
|
||||
)
|
||||
return plan
|
||||
Reference in New Issue
Block a user