1
0
forked from erp-dev/erp
Files
erpnew/api_v1/views/shipment/views.py

269 lines
8.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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
})