forked from erp-dev/erp
269 lines
8.6 KiB
Python
269 lines
8.6 KiB
Python
"""
|
||
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/<id>/
|
||
"""
|
||
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/<printing_order_id>/
|
||
|
||
返回与该 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
|
||
})
|