forked from erp-dev/erp
fix: appversions
This commit is contained in:
@@ -15,6 +15,7 @@ from .views import (
|
||||
ShipmentDeliveryBindShipmentsView,
|
||||
ShipmentDeliveryByPrintingOrderView,
|
||||
ShipmentDeliveryDetailView,
|
||||
ShipmentDeliveryPrintingJobListView,
|
||||
ShipmentDeliveryListCreateView,
|
||||
ShipmentDeliveryCancelView,
|
||||
ShipmentDeliveryStatusUpdateView,
|
||||
@@ -22,6 +23,7 @@ from .views import (
|
||||
ShipmentListCreateView,
|
||||
ShipmentDetailView,
|
||||
ShipmentExternalCreateView,
|
||||
ShipmentPrintingJobListView,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
@@ -36,6 +38,7 @@ __all__ = [
|
||||
'ShipmentDeliveryBindShipmentsView',
|
||||
'ShipmentDeliveryByPrintingOrderView',
|
||||
'ShipmentDeliveryDetailView',
|
||||
'ShipmentDeliveryPrintingJobListView',
|
||||
'ShipmentDeliveryListCreateView',
|
||||
'ShipmentDeliveryCancelView',
|
||||
'ShipmentDeliveryStatusUpdateView',
|
||||
@@ -43,4 +46,5 @@ __all__ = [
|
||||
'ShipmentListCreateView',
|
||||
'ShipmentDetailView',
|
||||
'ShipmentExternalCreateView',
|
||||
'ShipmentPrintingJobListView',
|
||||
]
|
||||
|
||||
@@ -650,6 +650,9 @@ class ShipmentSalesItemCustomerSerializer(serializers.Serializer):
|
||||
|
||||
class ShipmentDeliveryShipmentSummarySerializer(serializers.ModelSerializer):
|
||||
customer_name = serializers.CharField(source="customer.name", read_only=True)
|
||||
address_id = serializers.IntegerField(
|
||||
source="customer_address.id", read_only=True, allow_null=True
|
||||
)
|
||||
status_display = serializers.CharField(source="get_status_display", read_only=True)
|
||||
fabric = serializers.SerializerMethodField()
|
||||
order_description = serializers.SerializerMethodField()
|
||||
@@ -661,6 +664,14 @@ class ShipmentDeliveryShipmentSummarySerializer(serializers.ModelSerializer):
|
||||
"id",
|
||||
"customer",
|
||||
"customer_name",
|
||||
"address_id",
|
||||
"address",
|
||||
"contact_name",
|
||||
"contact_phone",
|
||||
"area",
|
||||
"coordinates",
|
||||
"geo_coordinates",
|
||||
"extra",
|
||||
"fabric",
|
||||
"order_description",
|
||||
"shipment_date",
|
||||
@@ -822,6 +833,21 @@ class ShipmentDeliveryByPrintingOrderSerializer(serializers.ModelSerializer):
|
||||
).data
|
||||
|
||||
|
||||
class ShipmentPrintingJobSummarySerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
printing_order_id = serializers.IntegerField(read_only=True)
|
||||
external_order_id = serializers.CharField(
|
||||
source="printing_order.external_order_id",
|
||||
read_only=True,
|
||||
allow_null=True,
|
||||
)
|
||||
customer_name = serializers.CharField(
|
||||
source="printing_order.customer.name",
|
||||
read_only=True,
|
||||
allow_null=True,
|
||||
)
|
||||
|
||||
|
||||
class ShipmentDeliveryCreateSerializer(serializers.Serializer):
|
||||
driver_name = serializers.CharField(max_length=100, help_text="司机名")
|
||||
vehicle_trip = serializers.CharField(max_length=100, help_text="车次")
|
||||
|
||||
@@ -2527,6 +2527,128 @@ class ShipmentQueryAPITestCase(TestCase):
|
||||
self.assertIn("external_finished_products", result)
|
||||
self.assertIsInstance(result["external_finished_products"], list)
|
||||
|
||||
def test_list_shipment_printing_jobs_returns_minimal_paginated_jobs(self):
|
||||
other_order = printing_models.PrintingOrder.objects.create(
|
||||
merchant=self.merchant1,
|
||||
customer=self.customer1,
|
||||
fabric="第二生产单面料",
|
||||
width="160cm",
|
||||
process=self.process1,
|
||||
created_by=self.user1,
|
||||
external_order_id="QUERY-PO-002",
|
||||
)
|
||||
other_job = printing_models.PrintingJob.objects.create(
|
||||
merchant=self.merchant1,
|
||||
printing_order=other_order,
|
||||
product=self.product1,
|
||||
quantity=10,
|
||||
unit="米",
|
||||
created_by=self.user1,
|
||||
)
|
||||
shipment_models.SalesItem.objects.create(
|
||||
merchant=self.merchant1,
|
||||
shipment=self.shipment1,
|
||||
name="重复销售品",
|
||||
quantity=Decimal("1.00"),
|
||||
unit=shipment_models.UnitChoices.METER,
|
||||
printing_job_id=self.printing_job1.id,
|
||||
customer_id=self.customer1.id,
|
||||
created_by=self.user1,
|
||||
)
|
||||
shipment_models.SalesItem.objects.create(
|
||||
merchant=self.merchant1,
|
||||
shipment=self.shipment1,
|
||||
name="第二销售品",
|
||||
quantity=Decimal("2.00"),
|
||||
unit=shipment_models.UnitChoices.METER,
|
||||
printing_job_id=other_job.id,
|
||||
customer_id=self.customer1.id,
|
||||
created_by=self.user1,
|
||||
)
|
||||
|
||||
resp = self.client.get(f"/api/v1/shipment/shipments/{self.shipment1.id}/printing-jobs/?limit=1")
|
||||
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
data = resp.json()
|
||||
self.assertEqual(data["count"], 2)
|
||||
self.assertEqual(len(data["results"]), 1)
|
||||
item = data["results"][0]
|
||||
self.assertEqual(set(item.keys()), {"id", "printing_order_id", "external_order_id", "customer_name"})
|
||||
self.assertEqual(item["id"], self.printing_job1.id)
|
||||
self.assertEqual(item["printing_order_id"], self.printing_order1.id)
|
||||
self.assertEqual(item["external_order_id"], "QUERY-PO-001")
|
||||
self.assertEqual(item["customer_name"], "客户1")
|
||||
|
||||
def test_list_delivery_printing_jobs_returns_jobs_across_shipments(self):
|
||||
delivery = shipment_models.ShipmentDelivery.objects.create(
|
||||
merchant=self.merchant1,
|
||||
driver_name="生产任务司机",
|
||||
vehicle_trip="JOB-DELIVERY",
|
||||
created_by=self.user1,
|
||||
)
|
||||
self.shipment1.delivery = delivery
|
||||
self.shipment1.save(update_fields=["delivery", "updated_at"])
|
||||
other_order = printing_models.PrintingOrder.objects.create(
|
||||
merchant=self.merchant1,
|
||||
customer=self.customer1,
|
||||
fabric="送货单生产单面料",
|
||||
width="160cm",
|
||||
process=self.process1,
|
||||
created_by=self.user1,
|
||||
external_order_id="QUERY-PO-DELIVERY",
|
||||
)
|
||||
other_job = printing_models.PrintingJob.objects.create(
|
||||
merchant=self.merchant1,
|
||||
printing_order=other_order,
|
||||
product=self.product1,
|
||||
quantity=10,
|
||||
unit="米",
|
||||
created_by=self.user1,
|
||||
)
|
||||
other_shipment = shipment_models.Shipment.objects.create(
|
||||
merchant=self.merchant1,
|
||||
customer=self.customer1,
|
||||
shipment_date="2026-01-24",
|
||||
created_by=self.user1,
|
||||
delivery=delivery,
|
||||
)
|
||||
shipment_models.SalesItem.objects.create(
|
||||
merchant=self.merchant1,
|
||||
shipment=other_shipment,
|
||||
name="送货单销售品",
|
||||
quantity=Decimal("2.00"),
|
||||
unit=shipment_models.UnitChoices.METER,
|
||||
printing_job_id=other_job.id,
|
||||
customer_id=self.customer1.id,
|
||||
created_by=self.user1,
|
||||
)
|
||||
|
||||
resp = self.client.get(f"/api/v1/shipment/deliveries/{delivery.id}/printing-jobs/?limit=10")
|
||||
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
data = resp.json()
|
||||
self.assertEqual(data["count"], 2)
|
||||
ids = [item["id"] for item in data["results"]]
|
||||
self.assertEqual(ids, [self.printing_job1.id, other_job.id])
|
||||
|
||||
def test_printing_jobs_sub_endpoints_respect_merchant_scope(self):
|
||||
other_delivery = shipment_models.ShipmentDelivery.objects.create(
|
||||
merchant=self.merchant2,
|
||||
driver_name="其他商户司机",
|
||||
vehicle_trip="OTHER-MERCHANT",
|
||||
created_by=self.user2,
|
||||
)
|
||||
|
||||
shipment_resp = self.client.get(
|
||||
f"/api/v1/shipment/shipments/{self.shipment2.id}/printing-jobs/"
|
||||
)
|
||||
delivery_resp = self.client.get(
|
||||
f"/api/v1/shipment/deliveries/{other_delivery.id}/printing-jobs/"
|
||||
)
|
||||
|
||||
self.assertEqual(shipment_resp.status_code, status.HTTP_404_NOT_FOUND)
|
||||
self.assertEqual(delivery_resp.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def test_patch_shipment_area_success(self):
|
||||
"""
|
||||
新增字段 area:支持更新(PATCH)并回显。
|
||||
@@ -3548,14 +3670,43 @@ class ShipmentDeliveryAPITestCase(APITestCase):
|
||||
self.assertEqual(data["results"][0]["id"], delivery.id)
|
||||
|
||||
def test_detail_returns_nested_shipment_summaries(self):
|
||||
customer_address = basic_models.CustomerAddress.objects.create(
|
||||
merchant=self.merchant,
|
||||
customer=self.customer,
|
||||
address="客户地址库地址",
|
||||
contact_name="地址库联系人",
|
||||
contact_phone="13600136000",
|
||||
area="杭州",
|
||||
coordinates="120.1551,30.2741",
|
||||
created_by=self.employee,
|
||||
)
|
||||
delivery = shipment_models.ShipmentDelivery.objects.create(
|
||||
merchant=self.merchant,
|
||||
driver_name="张司机",
|
||||
vehicle_trip="KD-004",
|
||||
created_by=self.user,
|
||||
)
|
||||
self.shipment1.customer_address = customer_address
|
||||
self.shipment1.address = "杭州市测试路 1 号"
|
||||
self.shipment1.contact_name = "张三"
|
||||
self.shipment1.contact_phone = "13800138000"
|
||||
self.shipment1.area = "华东"
|
||||
self.shipment1.coordinates = "120.1551,30.2741"
|
||||
self.shipment1.geo_coordinates = {"lng": 120.1551, "lat": 30.2741}
|
||||
self.shipment1.extra = {"dock": "A"}
|
||||
self.shipment1.delivery = delivery
|
||||
self.shipment1.save(update_fields=["delivery", "updated_at"])
|
||||
self.shipment1.save(update_fields=[
|
||||
"customer_address",
|
||||
"address",
|
||||
"contact_name",
|
||||
"contact_phone",
|
||||
"area",
|
||||
"coordinates",
|
||||
"geo_coordinates",
|
||||
"extra",
|
||||
"delivery",
|
||||
"updated_at",
|
||||
])
|
||||
self.shipment2.delivery = delivery
|
||||
self.shipment2.save(update_fields=["delivery", "updated_at"])
|
||||
|
||||
@@ -3572,6 +3723,14 @@ class ShipmentDeliveryAPITestCase(APITestCase):
|
||||
self.assertIn("fabric", item)
|
||||
self.assertIn("order_description", item)
|
||||
first_shipment = next(item for item in data["shipments"] if item["id"] == self.shipment1.id)
|
||||
self.assertEqual(first_shipment["address_id"], customer_address.id)
|
||||
self.assertEqual(first_shipment["address"], "杭州市测试路 1 号")
|
||||
self.assertEqual(first_shipment["contact_name"], "张三")
|
||||
self.assertEqual(first_shipment["contact_phone"], "13800138000")
|
||||
self.assertEqual(first_shipment["area"], "华东")
|
||||
self.assertEqual(first_shipment["coordinates"], "120.1551,30.2741")
|
||||
self.assertEqual(first_shipment["geo_coordinates"], {"lng": 120.1551, "lat": 30.2741})
|
||||
self.assertEqual(first_shipment["extra"], {"dock": "A"})
|
||||
self.assertIsNone(first_shipment["fabric"])
|
||||
self.assertIsNone(first_shipment["order_description"])
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ from rest_framework.views import APIView
|
||||
from django.utils.dateparse import parse_date, parse_datetime
|
||||
|
||||
from flower.viewsets import LimitedLimitOffsetPagination
|
||||
from shipment.models import Shipment, ShipmentDelivery, ShipmentDeliveryStatus, ShipmentStatus
|
||||
from shipment.models import SalesItem, Shipment, ShipmentDelivery, ShipmentDeliveryStatus, ShipmentStatus
|
||||
|
||||
from .serializers import (
|
||||
SalesItemDetailSerializer,
|
||||
@@ -26,6 +26,7 @@ from .serializers import (
|
||||
ShipmentDeliverySerializer,
|
||||
ShipmentDeliveryStatusUpdateSerializer,
|
||||
ShipmentDeliveryUpdateSerializer,
|
||||
ShipmentPrintingJobSummarySerializer,
|
||||
ShipmentSerializer,
|
||||
ShipmentCreateNormalSerializer,
|
||||
ShipmentCreateExternalSerializer,
|
||||
@@ -97,6 +98,26 @@ def _build_sales_item_serializer_context(items):
|
||||
}
|
||||
|
||||
|
||||
def _get_printing_jobs_for_shipment_ids(*, merchant, shipment_ids):
|
||||
from printing.models import PrintingJob
|
||||
|
||||
printing_job_ids = (
|
||||
SalesItem.objects.filter(
|
||||
merchant=merchant,
|
||||
shipment_id__in=shipment_ids,
|
||||
delete_at__isnull=True,
|
||||
printing_job_id__isnull=False,
|
||||
)
|
||||
.values_list("printing_job_id", flat=True)
|
||||
.distinct()
|
||||
)
|
||||
return (
|
||||
PrintingJob.objects.filter(id__in=printing_job_ids)
|
||||
.select_related("printing_order", "printing_order__customer")
|
||||
.order_by("id")
|
||||
)
|
||||
|
||||
|
||||
class ShipmentListCreateView(ListModelMixin, GenericAPIView):
|
||||
"""
|
||||
出货单:查询列表 / 创建
|
||||
@@ -417,6 +438,40 @@ class ShipmentStatusUpdateView(APIView):
|
||||
return self.patch(request, pk=pk)
|
||||
|
||||
|
||||
class ShipmentPrintingJobListView(GenericAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = ShipmentPrintingJobSummarySerializer
|
||||
pagination_class = LimitedLimitOffsetPagination
|
||||
|
||||
def get_shipment_queryset(self):
|
||||
qs = Shipment.objects.all().select_related("merchant")
|
||||
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):
|
||||
shipment = self.get_shipment_queryset().filter(id=pk).first()
|
||||
if shipment is None:
|
||||
return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
queryset = _get_printing_jobs_for_shipment_ids(
|
||||
merchant=shipment.merchant,
|
||||
shipment_ids=[shipment.id],
|
||||
)
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class ShipmentDeliveryListCreateView(ListModelMixin, GenericAPIView):
|
||||
"""
|
||||
送货单:查询列表 / 创建
|
||||
@@ -435,6 +490,7 @@ class ShipmentDeliveryListCreateView(ListModelMixin, GenericAPIView):
|
||||
).prefetch_related(
|
||||
"shipments",
|
||||
"shipments__customer",
|
||||
"shipments__customer_address",
|
||||
)
|
||||
|
||||
user = self.request.user
|
||||
@@ -489,6 +545,7 @@ class ShipmentDeliveryListCreateView(ListModelMixin, GenericAPIView):
|
||||
).prefetch_related(
|
||||
"shipments",
|
||||
"shipments__customer",
|
||||
"shipments__customer_address",
|
||||
).get(id=delivery.id)
|
||||
|
||||
return Response(
|
||||
@@ -652,7 +709,11 @@ class ShipmentDeliveryByPrintingOrderView(GenericAPIView):
|
||||
shipments__items__delete_at__isnull=True,
|
||||
)
|
||||
.select_related("merchant", "created_by", "operator", "cancelled_by")
|
||||
.prefetch_related("shipments", "shipments__customer")
|
||||
.prefetch_related(
|
||||
"shipments",
|
||||
"shipments__customer",
|
||||
"shipments__customer_address",
|
||||
)
|
||||
)
|
||||
queryset = self._apply_filters(queryset)
|
||||
|
||||
@@ -682,6 +743,7 @@ class ShipmentDeliveryDetailView(RetrieveModelMixin, GenericAPIView):
|
||||
).prefetch_related(
|
||||
"shipments",
|
||||
"shipments__customer",
|
||||
"shipments__customer_address",
|
||||
)
|
||||
|
||||
user = self.request.user
|
||||
@@ -744,6 +806,41 @@ class ShipmentDeliveryDetailView(RetrieveModelMixin, GenericAPIView):
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
class ShipmentDeliveryPrintingJobListView(GenericAPIView):
|
||||
permission_classes = [IsAuthenticated]
|
||||
serializer_class = ShipmentPrintingJobSummarySerializer
|
||||
pagination_class = LimitedLimitOffsetPagination
|
||||
|
||||
def get_delivery_queryset(self):
|
||||
qs = ShipmentDelivery.objects.all().select_related("merchant")
|
||||
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):
|
||||
delivery = self.get_delivery_queryset().filter(id=pk).first()
|
||||
if delivery is None:
|
||||
return Response({"detail": "Not found."}, status=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
shipment_ids = delivery.shipments.values_list("id", flat=True)
|
||||
queryset = _get_printing_jobs_for_shipment_ids(
|
||||
merchant=delivery.merchant,
|
||||
shipment_ids=shipment_ids,
|
||||
)
|
||||
page = self.paginate_queryset(queryset)
|
||||
if page is not None:
|
||||
serializer = self.get_serializer(page, many=True)
|
||||
return self.get_paginated_response(serializer.data)
|
||||
serializer = self.get_serializer(queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
|
||||
class ShipmentDeliveryStatusUpdateView(APIView):
|
||||
"""
|
||||
修改送货单状态
|
||||
@@ -787,6 +884,7 @@ class ShipmentDeliveryStatusUpdateView(APIView):
|
||||
).prefetch_related(
|
||||
"shipments",
|
||||
"shipments__customer",
|
||||
"shipments__customer_address",
|
||||
).get(id=delivery.id)
|
||||
|
||||
return Response(
|
||||
@@ -837,6 +935,7 @@ class ShipmentDeliveryCancelView(APIView):
|
||||
).prefetch_related(
|
||||
"shipments",
|
||||
"shipments__customer",
|
||||
"shipments__customer_address",
|
||||
).get(id=delivery.id)
|
||||
|
||||
return Response(
|
||||
@@ -888,6 +987,7 @@ class ShipmentDeliveryBindShipmentsView(APIView):
|
||||
).prefetch_related(
|
||||
"shipments",
|
||||
"shipments__customer",
|
||||
"shipments__customer_address",
|
||||
).get(id=delivery.id)
|
||||
|
||||
return Response(
|
||||
|
||||
Reference in New Issue
Block a user