forked from erp-dev/erp
feat: salesitem create api
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"""
|
||||
Shipment API ViewSet
|
||||
"""
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.response import Response
|
||||
@@ -23,10 +24,10 @@ from .serializers import (
|
||||
class ShipmentListCreateView(ListModelMixin, GenericAPIView):
|
||||
"""
|
||||
出货单:查询列表 / 创建
|
||||
|
||||
|
||||
- GET /api/v1/shipment/shipments/
|
||||
- POST /api/v1/shipment/shipments/
|
||||
|
||||
|
||||
请求体:
|
||||
{
|
||||
"customer": 1,
|
||||
@@ -34,7 +35,7 @@ class ShipmentListCreateView(ListModelMixin, GenericAPIView):
|
||||
"remark": "备注信息(可选)",
|
||||
"sales_items": [1, 2, 3]
|
||||
}
|
||||
|
||||
|
||||
返回:
|
||||
{
|
||||
"id": 1,
|
||||
@@ -49,6 +50,7 @@ class ShipmentListCreateView(ListModelMixin, GenericAPIView):
|
||||
"updated_at": "2026-01-14T10:00:00Z"
|
||||
}
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = ShipmentSerializer
|
||||
pagination_class = LimitedLimitOffsetPagination
|
||||
@@ -64,70 +66,73 @@ class ShipmentListCreateView(ListModelMixin, GenericAPIView):
|
||||
- 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',
|
||||
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 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')
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
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')
|
||||
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'],
|
||||
customer_id=data["customer"],
|
||||
shipment_date=data["shipment_date"],
|
||||
sales_item_ids=data["sales_items"],
|
||||
created_by=request.user,
|
||||
remark=data.get('remark', ''),
|
||||
area=data.get('area', ''),
|
||||
remark=data.get("remark", ""),
|
||||
area=data.get("area", ""),
|
||||
)
|
||||
except ValueError as e:
|
||||
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
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)
|
||||
@@ -139,23 +144,26 @@ 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',
|
||||
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):
|
||||
if getattr(user, "is_superuser", False):
|
||||
return qs
|
||||
|
||||
emp = getattr(user, 'employee', None)
|
||||
merchant = getattr(emp, 'merchant', None) if emp else None
|
||||
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)
|
||||
@@ -175,24 +183,30 @@ class ShipmentDetailView(RetrieveModelMixin, GenericAPIView):
|
||||
data = serializer.validated_data
|
||||
|
||||
# customer 变更需校验 merchant 一致
|
||||
if 'customer' in data:
|
||||
if "customer" in data:
|
||||
from basic_info.models import Customer
|
||||
|
||||
try:
|
||||
customer = Customer.objects.get(id=data['customer'])
|
||||
customer = Customer.objects.get(id=data["customer"])
|
||||
except Customer.DoesNotExist:
|
||||
return Response({'detail': f'客户 {data["customer"]} 不存在'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
{"detail": f"客户 {data['customer']} 不存在"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
if customer.merchant_id != shipment.merchant_id:
|
||||
return Response({'detail': '无权限绑定该客户'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
return Response(
|
||||
{"detail": "无权限绑定该客户"}, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
shipment.customer = customer
|
||||
|
||||
if 'shipment_date' in data:
|
||||
shipment.shipment_date = data['shipment_date']
|
||||
if 'area' in data:
|
||||
shipment.area = (data.get('area') or '').strip()
|
||||
if 'remark' in data:
|
||||
shipment.remark = data.get('remark') or ''
|
||||
if 'external_id' in data:
|
||||
shipment.external_id = (data.get('external_id') or '').strip() or None
|
||||
if "shipment_date" in data:
|
||||
shipment.shipment_date = data["shipment_date"]
|
||||
if "area" in data:
|
||||
shipment.area = (data.get("area") or "").strip()
|
||||
if "remark" in data:
|
||||
shipment.remark = data.get("remark") or ""
|
||||
if "external_id" in data:
|
||||
shipment.external_id = (data.get("external_id") or "").strip() or None
|
||||
|
||||
shipment.save()
|
||||
return Response(ShipmentSerializer(shipment).data, status=status.HTTP_200_OK)
|
||||
@@ -208,14 +222,15 @@ class ShipmentDetailView(RetrieveModelMixin, GenericAPIView):
|
||||
class ShipmentExternalCreateView(APIView):
|
||||
"""
|
||||
创建出货单(external 版)
|
||||
|
||||
|
||||
POST /api/v1/shipment/shipments/external/
|
||||
|
||||
|
||||
特点:
|
||||
- external_id 必填
|
||||
- external_finished_products 必填(数组)
|
||||
- 不绑定任何销售品
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
@@ -226,18 +241,19 @@ class ShipmentExternalCreateView(APIView):
|
||||
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'],
|
||||
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', ''),
|
||||
area=data.get('area', ''),
|
||||
remark=data.get("remark", ""),
|
||||
area=data.get("area", ""),
|
||||
)
|
||||
except ValueError as e:
|
||||
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
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)
|
||||
@@ -246,14 +262,14 @@ class ShipmentExternalCreateView(APIView):
|
||||
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,
|
||||
@@ -278,35 +294,105 @@ class SalesItemByPrintingOrderView(APIView):
|
||||
]
|
||||
}
|
||||
"""
|
||||
|
||||
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
|
||||
{"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'
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
return Response({"count": len(serializer.data), "results": serializer.data})
|
||||
|
||||
|
||||
class SalesItemCreateView(APIView):
|
||||
"""
|
||||
手动创建销售品
|
||||
|
||||
POST /api/v1/shipment/sales-items/
|
||||
|
||||
当自动转化销售品开关关闭时,通过此接口手动创建销售品。
|
||||
|
||||
请求体:
|
||||
{
|
||||
"printing_job_id": 123,
|
||||
"name": "产品名称",
|
||||
"quantity": "100.50",
|
||||
"unit": 1,
|
||||
"customer_id": 456,
|
||||
"remark": "备注信息",
|
||||
"position": "A1-01"
|
||||
}
|
||||
|
||||
参数说明:
|
||||
- printing_job_id: 生产任务ID(必填)
|
||||
- name: 销售品名称(必填)
|
||||
- quantity: 数量(必填,支持小数)
|
||||
- unit: 单位(必填):1=米, 2=件, 3=码, 4=个
|
||||
- customer_id: 客户ID(可选,默认从生产订单获取)
|
||||
- remark: 备注(可选)
|
||||
- position: 货位(可选)
|
||||
|
||||
返回:
|
||||
{
|
||||
"id": 1,
|
||||
"name": "产品名称",
|
||||
"quantity": "100.50",
|
||||
"unit": 1,
|
||||
"unit_display": "米",
|
||||
"position": "A1-01",
|
||||
"remark": "备注信息",
|
||||
"printing_job_id": 123,
|
||||
"customer_id": 456,
|
||||
"shipment_id": null,
|
||||
"shipment_date": null,
|
||||
"created_at": "2026-01-14T10:00:00Z",
|
||||
"created_by_id": 1,
|
||||
"created_by_name": "张三"
|
||||
}
|
||||
"""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
from .serializers import SalesItemCreateSerializer
|
||||
|
||||
serializer = SalesItemCreateSerializer(
|
||||
data=request.data, context={"request": request}
|
||||
)
|
||||
|
||||
if not serializer.is_valid():
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
sales_item = serializer.save()
|
||||
except ValueError as e:
|
||||
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
# 返回创建的销售品
|
||||
response_serializer = SalesItemSerializer(sales_item)
|
||||
return Response(response_serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
Reference in New Issue
Block a user