from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from django.db.models import Sum from .models import Crop, Variety, Plan, RiceTransplantPlan from .serializers import ( CropSerializer, VarietySerializer, PlanSerializer, RiceTransplantPlanSerializer, RiceTransplantPlanWriteSerializer, ) from .services import update_plan_with_variety_tracking from apps.fields.models import Field class CropViewSet(viewsets.ModelViewSet): queryset = Crop.objects.all() serializer_class = CropSerializer class VarietyViewSet(viewsets.ModelViewSet): queryset = Variety.objects.select_related('seed_material', 'crop').all() serializer_class = VarietySerializer class PlanViewSet(viewsets.ModelViewSet): queryset = Plan.objects.all() serializer_class = PlanSerializer def get_queryset(self): queryset = Plan.objects.all() year = self.request.query_params.get('year') if year: queryset = queryset.filter(year=year) return queryset @action(detail=False, methods=['get']) def summary(self, request): year = request.query_params.get('year') if not year: return Response({'error': 'year parameter is required'}, status=status.HTTP_400_BAD_REQUEST) plans = Plan.objects.filter(year=year) total_area = plans.aggregate(total=Sum('field__area_tan'))['total'] or 0 by_crop = {} for plan in plans: crop_name = plan.crop.name if crop_name not in by_crop: by_crop[crop_name] = { 'crop': crop_name, 'count': 0, 'area': 0 } by_crop[crop_name]['count'] += 1 by_crop[crop_name]['area'] += float(plan.field.area_tan) total_fields = Field.objects.count() assigned_field_ids = plans.values_list('field_id', flat=True).distinct() assigned_count = assigned_field_ids.count() unassigned_count = total_fields - assigned_count return Response({ 'year': int(year), 'total_fields': total_fields, 'assigned_fields': assigned_count, 'unassigned_fields': unassigned_count, 'total_plans': plans.count(), 'total_area': float(total_area), 'by_crop': list(by_crop.values()) }) @action(detail=False, methods=['post']) def copy_from_previous_year(self, request): from_year = request.data.get('from_year') to_year = request.data.get('to_year') if not from_year or not to_year: return Response({'error': 'from_year and to_year are required'}, status=status.HTTP_400_BAD_REQUEST) previous_plans = Plan.objects.filter(year=from_year) new_plans = [] for plan in previous_plans: new_plans.append(Plan( field=plan.field, year=to_year, crop=plan.crop, variety=plan.variety, notes=plan.notes )) Plan.objects.bulk_create(new_plans, ignore_conflicts=True) return Response({'message': f'Copied {len(new_plans)} plans from {from_year} to {to_year}'}) @action(detail=False, methods=['post']) def bulk_update(self, request): """複数圃場の作付け計画を一括更新""" field_ids = request.data.get('field_ids', []) year = request.data.get('year') crop_id = request.data.get('crop') variety_id = request.data.get('variety') if not field_ids or not year or not crop_id: return Response({'error': 'field_ids, year, crop are required'}, status=status.HTTP_400_BAD_REQUEST) try: crop = Crop.objects.get(id=crop_id) except Crop.DoesNotExist: return Response({'error': 'Crop not found'}, status=status.HTTP_400_BAD_REQUEST) variety = None if variety_id: try: variety = Variety.objects.get(id=variety_id) except Variety.DoesNotExist: pass updated = 0 created = 0 for field_id in field_ids: plan = Plan.objects.filter(field_id=field_id, year=year).first() if plan is None: Plan.objects.create( field_id=field_id, year=year, crop=crop, variety=variety, ) created += 1 continue update_plan_with_variety_tracking( plan, crop=crop, variety=variety, ) updated += 1 return Response({'created': created, 'updated': updated, 'total': created + updated}) @action(detail=False, methods=['get']) def get_crops_with_varieties(self, request): crops = Crop.objects.prefetch_related('varieties__seed_material').all() return Response(CropSerializer(crops, many=True).data) class RiceTransplantPlanViewSet(viewsets.ModelViewSet): queryset = RiceTransplantPlan.objects.select_related( 'variety', 'variety__crop', 'variety__seed_material', ).prefetch_related( 'variety__seed_material__stock_transactions', 'entries', 'entries__field', ) def get_queryset(self): queryset = self.queryset year = self.request.query_params.get('year') if year: queryset = queryset.filter(year=year) return queryset def get_serializer_class(self): if self.action in ['create', 'update', 'partial_update']: return RiceTransplantPlanWriteSerializer return RiceTransplantPlanSerializer @action(detail=False, methods=['get']) def candidate_fields(self, request): year = request.query_params.get('year') variety_id = request.query_params.get('variety_id') if not year or not variety_id: return Response( {'error': 'year と variety_id が必要です'}, status=status.HTTP_400_BAD_REQUEST, ) field_ids = Plan.objects.filter( year=year, variety_id=variety_id, ).values_list('field_id', flat=True) fields = Field.objects.filter(id__in=field_ids).order_by('display_order', 'id') data = [ { 'id': field.id, 'name': field.name, 'area_tan': str(field.area_tan), 'area_m2': field.area_m2, 'group_name': field.group_name, } for field in fields ] return Response(data)