1
0
forked from erp-dev/erp

feat: shipment_delivery

This commit is contained in:
2026-04-03 16:55:22 +08:00
parent 371cd796e7
commit 76b30cf4f4
16 changed files with 2513 additions and 9 deletions

View File

@@ -10,11 +10,16 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from flower.viewsets import LimitedLimitOffsetPagination
from shipment.models import Shipment
from shipment.models import Shipment, ShipmentDelivery
from .serializers import (
SalesItemDetailSerializer,
SalesItemSerializer,
ShipmentDeliveryBindShipmentsSerializer,
ShipmentDeliveryCreateSerializer,
ShipmentDeliverySerializer,
ShipmentDeliveryStatusUpdateSerializer,
ShipmentDeliveryUpdateSerializer,
ShipmentSerializer,
ShipmentCreateNormalSerializer,
ShipmentCreateExternalSerializer,
@@ -178,6 +183,9 @@ class ShipmentListCreateView(ListModelMixin, GenericAPIView):
shipment_date=data["shipment_date"],
sales_item_ids=data["sales_items"],
created_by=request.user,
address=data.get("address", ""),
contact_name=data.get("contact_name", ""),
contact_phone=data.get("contact_phone", ""),
remark=data.get("remark", ""),
area=data.get("area", ""),
)
@@ -246,6 +254,9 @@ class ShipmentDetailView(RetrieveModelMixin, GenericAPIView):
shipment,
customer_id=data.get("customer"),
shipment_date=data.get("shipment_date"),
address=data.get("address"),
contact_name=data.get("contact_name"),
contact_phone=data.get("contact_phone"),
area=data.get("area"),
remark=data.get("remark"),
external_id=data.get("external_id"),
@@ -263,6 +274,313 @@ class ShipmentDetailView(RetrieveModelMixin, GenericAPIView):
return self.patch(request, pk=pk)
class ShipmentDeliveryListCreateView(ListModelMixin, GenericAPIView):
"""
送货单:查询列表 / 创建
- GET /api/v1/shipment/deliveries/
- POST /api/v1/shipment/deliveries/
"""
permission_classes = [IsAuthenticated]
serializer_class = ShipmentDeliverySerializer
pagination_class = LimitedLimitOffsetPagination
def get_queryset(self):
qs = ShipmentDelivery.objects.all().select_related(
"merchant", "created_by", "operator", "cancelled_by"
).prefetch_related(
"shipments",
"shipments__customer",
)
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 ShipmentDelivery.objects.none()
qs = qs.filter(merchant=merchant)
status_val = self.request.query_params.get("status")
if status_val:
qs = qs.filter(status=status_val)
driver_name = (self.request.query_params.get("driver_name") or "").strip()
if driver_name:
qs = qs.filter(driver_name__icontains=driver_name)
vehicle_trip = (self.request.query_params.get("vehicle_trip") or "").strip()
if vehicle_trip:
qs = qs.filter(vehicle_trip__icontains=vehicle_trip)
return qs.order_by("-created_at", "-id")
def get(self, request):
return self.list(request)
def post(self, request):
serializer = ShipmentDeliveryCreateSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
from shipment.services import create_shipment_delivery
try:
delivery = create_shipment_delivery(
driver_name=serializer.validated_data["driver_name"],
vehicle_trip=serializer.validated_data["vehicle_trip"],
contact_phone=serializer.validated_data.get("contact_phone", ""),
vehicle_capacity=serializer.validated_data.get("vehicle_capacity", ""),
shipment_ids=serializer.validated_data.get("shipments", []),
created_by=request.user,
remark=serializer.validated_data.get("remark", ""),
internal_remark=serializer.validated_data.get("internal_remark", ""),
)
except ValueError as e:
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
delivery = ShipmentDelivery.objects.select_related(
"merchant", "created_by", "operator", "cancelled_by"
).prefetch_related(
"shipments",
"shipments__customer",
).get(id=delivery.id)
return Response(
ShipmentDeliverySerializer(delivery).data,
status=status.HTTP_201_CREATED,
)
class ShipmentDeliveryDetailView(RetrieveModelMixin, GenericAPIView):
"""
送货单详情 / 修改 / 删除
- GET /api/v1/shipment/deliveries/<id>/
- PATCH /api/v1/shipment/deliveries/<id>/
- PUT /api/v1/shipment/deliveries/<id>/
- DELETE /api/v1/shipment/deliveries/<id>/
"""
permission_classes = [IsAuthenticated]
serializer_class = ShipmentDeliverySerializer
def get_queryset(self):
qs = ShipmentDelivery.objects.all().select_related(
"merchant", "created_by", "operator", "cancelled_by"
).prefetch_related(
"shipments",
"shipments__customer",
)
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 ShipmentDelivery.objects.none()
return qs.filter(merchant=merchant)
def get(self, request, pk: int):
return self.retrieve(request, pk=pk)
def patch(self, request, pk: int):
delivery = self.get_object()
serializer = ShipmentDeliveryUpdateSerializer(data=request.data, partial=True)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
from shipment.services import update_shipment_delivery
try:
delivery = update_shipment_delivery(
delivery,
driver_name=serializer.validated_data.get("driver_name"),
vehicle_trip=serializer.validated_data.get("vehicle_trip"),
contact_phone=serializer.validated_data.get("contact_phone"),
vehicle_capacity=serializer.validated_data.get("vehicle_capacity"),
shipment_ids=serializer.validated_data.get("shipments"),
remark=serializer.validated_data.get("remark"),
internal_remark=serializer.validated_data.get("internal_remark"),
operator=request.user,
)
except ValueError as e:
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
delivery = self.get_queryset().get(id=delivery.id)
return Response(
ShipmentDeliverySerializer(delivery).data,
status=status.HTTP_200_OK,
)
def put(self, request, pk: int):
return self.patch(request, pk=pk)
def delete(self, request, pk: int):
delivery = self.get_object()
from shipment.services import delete_shipment_delivery
delete_shipment_delivery(delivery)
return Response(status=status.HTTP_204_NO_CONTENT)
class ShipmentDeliveryStatusUpdateView(APIView):
"""
修改送货单状态
POST /api/v1/shipment/deliveries/<id>/status/
"""
permission_classes = [IsAuthenticated]
def post(self, request, pk: int):
user = request.user
queryset = ShipmentDelivery.objects.all()
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 Response({"detail": "未找到送货单"}, status=status.HTTP_404_NOT_FOUND)
queryset = queryset.filter(merchant=merchant)
delivery = queryset.filter(id=pk).first()
if delivery is None:
return Response({"detail": "未找到送货单"}, status=status.HTTP_404_NOT_FOUND)
serializer = ShipmentDeliveryStatusUpdateSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
from shipment.services import modify_shipment_delivery_status
try:
delivery = modify_shipment_delivery_status(
delivery,
target_status=serializer.validated_data["status"],
operator=request.user,
)
except ValueError as e:
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
delivery = ShipmentDelivery.objects.select_related(
"merchant", "created_by", "operator", "cancelled_by"
).prefetch_related(
"shipments",
"shipments__customer",
).get(id=delivery.id)
return Response(
ShipmentDeliverySerializer(delivery).data,
status=status.HTTP_200_OK,
)
class ShipmentDeliveryCancelView(APIView):
"""
取消送货单
POST /api/v1/shipment/deliveries/<id>/cancel/
"""
permission_classes = [IsAuthenticated]
def post(self, request, pk: int):
if not request.user.has_perm("shipment.cancel_shipmentdelivery"):
return Response(
{"detail": "没有权限取消送货单"},
status=status.HTTP_403_FORBIDDEN,
)
user = request.user
queryset = ShipmentDelivery.objects.all()
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 Response({"detail": "未找到送货单"}, status=status.HTTP_404_NOT_FOUND)
queryset = queryset.filter(merchant=merchant)
delivery = queryset.filter(id=pk).first()
if delivery is None:
return Response({"detail": "未找到送货单"}, status=status.HTTP_404_NOT_FOUND)
from shipment.services import cancel_shipment_delivery
delivery = cancel_shipment_delivery(
delivery,
cancelled_by=request.user,
operator=request.user,
)
delivery = ShipmentDelivery.objects.select_related(
"merchant", "created_by", "operator", "cancelled_by"
).prefetch_related(
"shipments",
"shipments__customer",
).get(id=delivery.id)
return Response(
ShipmentDeliverySerializer(delivery).data,
status=status.HTTP_200_OK,
)
class ShipmentDeliveryBindShipmentsView(APIView):
"""
追加绑定出货单到现有送货单
POST /api/v1/shipment/deliveries/<id>/bind-shipments/
"""
permission_classes = [IsAuthenticated]
def post(self, request, pk: int):
user = request.user
queryset = ShipmentDelivery.objects.all()
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 Response({"detail": "未找到送货单"}, status=status.HTTP_404_NOT_FOUND)
queryset = queryset.filter(merchant=merchant)
delivery = queryset.filter(id=pk).first()
if delivery is None:
return Response({"detail": "未找到送货单"}, status=status.HTTP_404_NOT_FOUND)
serializer = ShipmentDeliveryBindShipmentsSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
from shipment.services import bind_shipments_to_delivery
try:
delivery = bind_shipments_to_delivery(
delivery,
shipment_ids=serializer.validated_data["shipments"],
operator=request.user,
)
except ValueError as e:
return Response({"detail": str(e)}, status=status.HTTP_400_BAD_REQUEST)
delivery = ShipmentDelivery.objects.select_related(
"merchant", "created_by", "operator", "cancelled_by"
).prefetch_related(
"shipments",
"shipments__customer",
).get(id=delivery.id)
return Response(
ShipmentDeliverySerializer(delivery).data,
status=status.HTTP_200_OK,
)
class ShipmentExternalCreateView(APIView):
"""
创建出货单external 版)
@@ -293,6 +611,9 @@ class ShipmentExternalCreateView(APIView):
external_id=data["external_id"],
external_finished_products=data["external_finished_products"],
created_by=request.user,
address=data.get("address", ""),
contact_name=data.get("contact_name", ""),
contact_phone=data.get("contact_phone", ""),
remark=data.get("remark", ""),
area=data.get("area", ""),
)