畔塗記録 API に total_area_tan を追加して、一覧の各記録に「圃場数 / 面積合計」が出るようにしました。あわせて、作成・編集フォームの「対象圃場一覧」にも、選択中の合計面積を表示しています。主な変更は serializers.py、tests.py、page.tsx、index.ts です。 確認できたこと: docker compose -f docker-compose.develop.yml exec backend python manage.py test apps.levee_work OK docker exec keinasystem_frontend npm run build OK まだコミットはしていません。必要ならこのままコミットして push します。
150 lines
5.3 KiB
Python
150 lines
5.3 KiB
Python
from django.db import transaction
|
|
from decimal import Decimal
|
|
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()
|
|
total_area_tan = serializers.SerializerMethodField()
|
|
|
|
class Meta:
|
|
model = LeveeWorkSession
|
|
fields = [
|
|
'id',
|
|
'year',
|
|
'date',
|
|
'title',
|
|
'notes',
|
|
'work_record_id',
|
|
'item_count',
|
|
'total_area_tan',
|
|
'items',
|
|
'created_at',
|
|
'updated_at',
|
|
]
|
|
|
|
def get_item_count(self, obj):
|
|
return len(obj.items.all())
|
|
|
|
def get_total_area_tan(self, obj):
|
|
total = sum((item.field.area_tan or Decimal('0')) for item in obj.items.all())
|
|
return str(total)
|
|
|
|
|
|
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
|