""" Shipment API ViewSet """ from rest_framework import status from rest_framework.views import APIView from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated from rest_framework.generics import GenericAPIView from rest_framework.mixins import ListModelMixin, RetrieveModelMixin from flower.viewsets import LimitedLimitOffsetPagination from shipment.models import Shipment from .serializers import ( SalesItemSerializer, ShipmentSerializer, ShipmentCreateNormalSerializer, ShipmentCreateExternalSerializer, ) class ShipmentListCreateView(ListModelMixin, GenericAPIView): """ 出货单:查询列表 / 创建 - GET /api/v1/shipment/shipments/ - POST /api/v1/shipment/shipments/ 请求体: { "customer": 1, "shipment_date": "2026-01-14", "remark": "备注信息(可选)", "sales_items": [1, 2, 3] } 返回: { "id": 1, "customer": 1, "customer_name": "客户A", "shipment_date": "2026-01-14", "remark": "备注信息", "items_count": 3, "created_by_id": 1, "created_by_name": "张三", "created_at": "2026-01-14T10:00:00Z", "updated_at": "2026-01-14T10:00:00Z" } """ permission_classes = [IsAuthenticated] serializer_class = ShipmentSerializer pagination_class = LimitedLimitOffsetPagination def get_queryset(self): """ 仅返回当前用户所属商户的出货单(merchant 隔离)。 支持过滤参数(可选): - customer: 客户ID - status: 状态(1/2/3) - external_id: 外部订单号(精确匹配) - shipment_date_from: 出货日期起始(YYYY-MM-DD) - shipment_date_to: 出货日期结束(YYYY-MM-DD,包含整天) """ qs = Shipment.objects.all().select_related( 'merchant', 'customer', 'created_by', 'cancelled_by' ).prefetch_related( 'items', 'external_finished_products', ) user = self.request.user if not getattr(user, 'is_superuser', False): emp = getattr(user, 'employee', None) merchant = getattr(emp, 'merchant', None) if emp else None if not merchant: return Shipment.objects.none() qs = qs.filter(merchant=merchant) # optional filters customer_id = self.request.query_params.get('customer') if customer_id: qs = qs.filter(customer_id=customer_id) status_val = self.request.query_params.get('status') if status_val: qs = qs.filter(status=status_val) external_id = self.request.query_params.get('external_id') if external_id: qs = qs.filter(external_id=external_id) date_from = self.request.query_params.get('shipment_date_from') if date_from: qs = qs.filter(shipment_date__gte=date_from) date_to = self.request.query_params.get('shipment_date_to') if date_to: # 通过 <= 过滤日期 qs = qs.filter(shipment_date__lte=date_to) return qs.order_by('-created_at', '-id') def get(self, request): return self.list(request) def post(self, request): # 验证请求数据 serializer = ShipmentCreateNormalSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) data = serializer.validated_data # 调用业务逻辑 from shipment.services import create_shipment try: shipment = create_shipment( customer_id=data['customer'], shipment_date=data['shipment_date'], sales_item_ids=data['sales_items'], created_by=request.user, remark=data.get('remark', ''), ) except ValueError as e: return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST) # 返回创建的出货单 response_serializer = ShipmentSerializer(shipment) return Response(response_serializer.data, status=status.HTTP_201_CREATED) class ShipmentDetailView(RetrieveModelMixin, GenericAPIView): """ 出货单详情 GET /api/v1/shipment/shipments// """ permission_classes = [IsAuthenticated] serializer_class = ShipmentSerializer def get_queryset(self): qs = Shipment.objects.all().select_related( 'merchant', 'customer', 'created_by', 'cancelled_by' ).prefetch_related( 'items', 'external_finished_products', ) user = self.request.user if getattr(user, 'is_superuser', False): return qs emp = getattr(user, 'employee', None) merchant = getattr(emp, 'merchant', None) if emp else None if not merchant: return Shipment.objects.none() return qs.filter(merchant=merchant) def get(self, request, pk: int): return self.retrieve(request, pk=pk) class ShipmentExternalCreateView(APIView): """ 创建出货单(external 版) POST /api/v1/shipment/shipments/external/ 特点: - external_id 必填 - external_finished_products 必填(数组) - 不绑定任何销售品 """ permission_classes = [IsAuthenticated] def post(self, request): serializer = ShipmentCreateExternalSerializer(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) data = serializer.validated_data from shipment.services import create_external_shipment try: shipment = create_external_shipment( customer_id=data['customer'], shipment_date=data['shipment_date'], external_id=data['external_id'], external_finished_products=data['external_finished_products'], created_by=request.user, remark=data.get('remark', ''), ) except ValueError as e: return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST) response_serializer = ShipmentSerializer(shipment) return Response(response_serializer.data, status=status.HTTP_201_CREATED) class SalesItemByPrintingOrderView(APIView): """ 通过生产订单查询销售品 GET /api/v1/shipment/sales-items/by-printing-order// 返回与该 PrintingOrder 下所有 PrintingJob 关联的 SalesItem 列表。 查询参数: - include_already_has_shipment: 是否包含已关联出货单的销售品(true/false),默认 false 返回: { "count": 5, "results": [ { "id": 1, "name": "产品A", "quantity": "100.00", "unit": 1, "unit_display": "米", "position": "A1-01", "remark": "", "printing_job_id": 123, "customer_id": null, "shipment_id": null, "shipment_date": null, "created_at": "2026-01-14T10:00:00Z", "created_by_id": 1, "created_by_name": "张三" }, ... ] } """ permission_classes = [IsAuthenticated] def get(self, request, printing_order_id): # 验证生产订单是否存在 from printing.models import PrintingOrder try: printing_order = PrintingOrder.objects.get(id=printing_order_id) except PrintingOrder.DoesNotExist: return Response( {'detail': f'生产订单 {printing_order_id} 不存在'}, status=status.HTTP_404_NOT_FOUND ) # 获取查询参数 include_already_has_shipment = request.query_params.get( 'include_already_has_shipment', 'false' ).lower() == 'true' # 调用 shipment 业务逻辑 from shipment.services import get_sales_items_by_printing_order sales_items = get_sales_items_by_printing_order( printing_order_id=printing_order.id, include_already_has_shipment=include_already_has_shipment, ) # 序列化返回 serializer = SalesItemSerializer(sales_items, many=True) return Response({ 'count': len(serializer.data), 'results': serializer.data })