from decimal import Decimal from rest_framework import generics, status, viewsets from rest_framework.permissions import IsAuthenticated from rest_framework.response import Response from .models import Material, StockTransaction from .serializers import ( MaterialReadSerializer, MaterialWriteSerializer, StockSummarySerializer, StockTransactionSerializer, ) class MaterialViewSet(viewsets.ModelViewSet): """資材マスタ CRUD""" permission_classes = [IsAuthenticated] def get_queryset(self): queryset = Material.objects.select_related( 'fertilizer_profile', 'pesticide_profile', ).prefetch_related('stock_transactions') material_type = self.request.query_params.get('material_type') if material_type: queryset = queryset.filter(material_type=material_type) active = self.request.query_params.get('active') if active is not None: queryset = queryset.filter(is_active=active.lower() == 'true') return queryset def get_serializer_class(self): if self.action in ['create', 'update', 'partial_update']: return MaterialWriteSerializer return MaterialReadSerializer def destroy(self, request, *args, **kwargs): instance = self.get_object() if instance.stock_transactions.exists(): return Response( {'detail': 'この資材には入出庫履歴があるため削除できません。無効化してください。'}, status=status.HTTP_400_BAD_REQUEST, ) return super().destroy(request, *args, **kwargs) class StockTransactionViewSet(viewsets.ModelViewSet): """入出庫履歴 CRUD""" serializer_class = StockTransactionSerializer permission_classes = [IsAuthenticated] http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options'] def get_queryset(self): queryset = StockTransaction.objects.select_related('material') material_id = self.request.query_params.get('material_id') if material_id: queryset = queryset.filter(material_id=material_id) material_type = self.request.query_params.get('material_type') if material_type: queryset = queryset.filter(material__material_type=material_type) date_from = self.request.query_params.get('date_from') if date_from: queryset = queryset.filter(occurred_on__gte=date_from) date_to = self.request.query_params.get('date_to') if date_to: queryset = queryset.filter(occurred_on__lte=date_to) return queryset def update(self, request, *args, **kwargs): instance = self.get_object() if instance.fertilization_plan_id or instance.spreading_item_id: return Response( {'detail': '計画や実績に紐づく入出庫履歴は編集できません。'}, status=status.HTTP_400_BAD_REQUEST, ) return super().update(request, *args, **kwargs) def partial_update(self, request, *args, **kwargs): instance = self.get_object() if instance.fertilization_plan_id or instance.spreading_item_id: return Response( {'detail': '計画や実績に紐づく入出庫履歴は編集できません。'}, status=status.HTTP_400_BAD_REQUEST, ) return super().partial_update(request, *args, **kwargs) def destroy(self, request, *args, **kwargs): instance = self.get_object() if instance.fertilization_plan_id or instance.spreading_item_id: return Response( {'detail': '計画や実績に紐づく入出庫履歴は削除できません。'}, status=status.HTTP_400_BAD_REQUEST, ) return super().destroy(request, *args, **kwargs) class StockSummaryView(generics.ListAPIView): """在庫集計一覧""" serializer_class = StockSummarySerializer permission_classes = [IsAuthenticated] def get_queryset(self): return Material.objects.none() def list(self, request, *args, **kwargs): queryset = Material.objects.prefetch_related('stock_transactions').order_by( 'material_type', 'name', ) material_type = request.query_params.get('material_type') if material_type: queryset = queryset.filter(material_type=material_type) active = request.query_params.get('active') if active is not None: queryset = queryset.filter(is_active=active.lower() == 'true') results = [] for material in queryset: results.append(_build_stock_summary(material)) serializer = self.get_serializer(results, many=True) return Response(serializer.data) class FertilizerStockView(generics.ListAPIView): """施肥計画画面用: 肥料の在庫情報を返す""" permission_classes = [IsAuthenticated] serializer_class = StockSummarySerializer def get_queryset(self): return Material.objects.none() def list(self, request, *args, **kwargs): queryset = Material.objects.filter( material_type=Material.MaterialType.FERTILIZER, is_active=True, ).prefetch_related('stock_transactions').order_by('name') results = [_build_stock_summary(material) for material in queryset] serializer = self.get_serializer(results, many=True) return Response(serializer.data) def _build_stock_summary(material): transactions = list(material.stock_transactions.all()) increase = sum( txn.quantity for txn in transactions if txn.transaction_type in StockTransaction.INCREASE_TYPES ) decrease = sum( txn.quantity for txn in transactions if txn.transaction_type in StockTransaction.DECREASE_TYPES ) reserved = sum( txn.quantity for txn in transactions if txn.transaction_type == StockTransaction.TransactionType.RESERVE ) available = increase - decrease if transactions else Decimal('0') last_date = max((txn.occurred_on for txn in transactions), default=None) return { 'material_id': material.id, 'name': material.name, 'material_type': material.material_type, 'material_type_display': material.get_material_type_display(), 'maker': material.maker, 'stock_unit': material.stock_unit, 'stock_unit_display': material.get_stock_unit_display(), 'is_active': material.is_active, 'current_stock': available + reserved, 'reserved_stock': reserved, 'available_stock': available, 'last_transaction_date': last_date, }