forked from erp-dev/erp
fix: added delivery_id and delivery_isnull query param to shipment list api
This commit is contained in:
@@ -85,6 +85,7 @@ class ShipmentSerializer(serializers.ModelSerializer):
|
||||
external_finished_products_count = serializers.SerializerMethodField()
|
||||
merchant_id = serializers.IntegerField(source="merchant.id", read_only=True)
|
||||
merchant_name = serializers.CharField(source="merchant.name", read_only=True)
|
||||
delivery_id = serializers.IntegerField(read_only=True, allow_null=True)
|
||||
sales_items = serializers.SerializerMethodField()
|
||||
external_finished_products = serializers.SerializerMethodField()
|
||||
|
||||
@@ -106,6 +107,7 @@ class ShipmentSerializer(serializers.ModelSerializer):
|
||||
"status_display",
|
||||
"external_id",
|
||||
"geo_coordinates",
|
||||
"delivery_id",
|
||||
"status_modified_at",
|
||||
"cancelled_by_id",
|
||||
"cancelled_by_name",
|
||||
@@ -492,6 +494,7 @@ class ShipmentSalesItemCustomerSerializer(serializers.Serializer):
|
||||
class ShipmentDeliveryShipmentSummarySerializer(serializers.ModelSerializer):
|
||||
customer_name = serializers.CharField(source="customer.name", read_only=True)
|
||||
status_display = serializers.CharField(source="get_status_display", read_only=True)
|
||||
delivery_id = serializers.IntegerField(read_only=True, allow_null=True)
|
||||
|
||||
class Meta:
|
||||
model = Shipment
|
||||
@@ -503,6 +506,7 @@ class ShipmentDeliveryShipmentSummarySerializer(serializers.ModelSerializer):
|
||||
"status",
|
||||
"status_display",
|
||||
"external_id",
|
||||
"delivery_id",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
@@ -759,6 +759,11 @@ class SalesItemDetailAPITestCase(APITestCase):
|
||||
mobile="13800138033",
|
||||
status=basic_models.EmployeeStatusEnum.ACTIVE,
|
||||
)
|
||||
self.no_employee_user = User.objects.create_user(
|
||||
username="sales_item_detail_no_employee_user",
|
||||
password="testpass123",
|
||||
email="sales_item_detail_no_employee@example.com",
|
||||
)
|
||||
|
||||
self.customer = basic_models.Customer.objects.create(
|
||||
merchant=self.merchant,
|
||||
@@ -1110,6 +1115,19 @@ class SalesItemDetailAPITestCase(APITestCase):
|
||||
self.assertEqual(resp.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
self.assertEqual(resp.json()["detail"], "已关联出货单的销售品不允许删除")
|
||||
|
||||
def test_rebuild_sales_item_user_without_employee_returns_404(self):
|
||||
self.client.force_authenticate(user=self.no_employee_user)
|
||||
|
||||
resp = self.client.post(
|
||||
f"/api/v1/shipment/sales-items/{self.sales_item.id}/rebuild/",
|
||||
{
|
||||
"new_printing_job_id": self.new_printing_job.id,
|
||||
},
|
||||
format="json",
|
||||
)
|
||||
|
||||
self.assertEqual(resp.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
class ShipmentCreateAPITestCase(TestCase):
|
||||
"""测试创建出货单 API"""
|
||||
@@ -1672,6 +1690,8 @@ class ShipmentQueryAPITestCase(TestCase):
|
||||
self.assertIn("contact_name", item)
|
||||
self.assertIn("contact_phone", item)
|
||||
self.assertIn("area", item)
|
||||
self.assertIn("delivery_id", item)
|
||||
self.assertIsNone(item["delivery_id"])
|
||||
self.assertEqual(item["area"], "A1")
|
||||
self.assertEqual(item["status"], shipment_models.ShipmentStatus.DRAFT)
|
||||
self.assertEqual(item["status_display"], "草稿(未发布)")
|
||||
@@ -1707,6 +1727,107 @@ class ShipmentQueryAPITestCase(TestCase):
|
||||
self.assertIn(shipment_with_address.id, false_ids)
|
||||
self.assertNotIn(self.shipment1.id, false_ids)
|
||||
|
||||
def test_list_shipments_supports_delivery_id_filter(self):
|
||||
delivery = shipment_models.ShipmentDelivery.objects.create(
|
||||
merchant=self.merchant1,
|
||||
driver_name="过滤司机",
|
||||
vehicle_trip="FILTER-001",
|
||||
created_by=self.user1,
|
||||
)
|
||||
self.shipment1.delivery = delivery
|
||||
self.shipment1.save(update_fields=["delivery", "updated_at"])
|
||||
|
||||
other_same_merchant = shipment_models.Shipment.objects.create(
|
||||
merchant=self.merchant1,
|
||||
customer=self.customer1,
|
||||
shipment_date="2026-01-17",
|
||||
created_by=self.user1,
|
||||
remark="other",
|
||||
area="A2",
|
||||
)
|
||||
|
||||
resp = self.client.get(f"/api/v1/shipment/shipments/?delivery_id={delivery.id}")
|
||||
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
data = resp.json()
|
||||
self.assertEqual(data["count"], 1)
|
||||
self.assertEqual(data["results"][0]["id"], self.shipment1.id)
|
||||
self.assertEqual(data["results"][0]["delivery_id"], delivery.id)
|
||||
self.assertNotEqual(other_same_merchant.id, self.shipment1.id)
|
||||
|
||||
def test_list_shipments_ignores_null_like_delivery_id_filter(self):
|
||||
delivery = shipment_models.ShipmentDelivery.objects.create(
|
||||
merchant=self.merchant1,
|
||||
driver_name="空值司机",
|
||||
vehicle_trip="FILTER-NULL",
|
||||
created_by=self.user1,
|
||||
)
|
||||
self.shipment1.delivery = delivery
|
||||
self.shipment1.save(update_fields=["delivery", "updated_at"])
|
||||
|
||||
resp = self.client.get("/api/v1/shipment/shipments/?delivery_id=null")
|
||||
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
data = resp.json()
|
||||
ids = [it["id"] for it in data["results"]]
|
||||
self.assertIn(self.shipment1.id, ids)
|
||||
|
||||
shipment_item = next(it for it in data["results"] if it["id"] == self.shipment1.id)
|
||||
self.assertEqual(shipment_item["delivery_id"], delivery.id)
|
||||
|
||||
def test_list_shipments_supports_delivery_isnull_true_filter(self):
|
||||
delivery = shipment_models.ShipmentDelivery.objects.create(
|
||||
merchant=self.merchant1,
|
||||
driver_name="未绑定筛选司机",
|
||||
vehicle_trip="FILTER-ISNULL-TRUE",
|
||||
created_by=self.user1,
|
||||
)
|
||||
self.shipment1.delivery = delivery
|
||||
self.shipment1.save(update_fields=["delivery", "updated_at"])
|
||||
|
||||
shipment_without_delivery = shipment_models.Shipment.objects.create(
|
||||
merchant=self.merchant1,
|
||||
customer=self.customer1,
|
||||
shipment_date="2026-01-18",
|
||||
created_by=self.user1,
|
||||
remark="no-delivery",
|
||||
area="A3",
|
||||
)
|
||||
|
||||
resp = self.client.get("/api/v1/shipment/shipments/?delivery_isnull=true")
|
||||
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
ids = [it["id"] for it in resp.json()["results"]]
|
||||
self.assertIn(shipment_without_delivery.id, ids)
|
||||
self.assertNotIn(self.shipment1.id, ids)
|
||||
|
||||
def test_list_shipments_supports_delivery_isnull_false_filter(self):
|
||||
delivery = shipment_models.ShipmentDelivery.objects.create(
|
||||
merchant=self.merchant1,
|
||||
driver_name="已绑定筛选司机",
|
||||
vehicle_trip="FILTER-ISNULL-FALSE",
|
||||
created_by=self.user1,
|
||||
)
|
||||
self.shipment1.delivery = delivery
|
||||
self.shipment1.save(update_fields=["delivery", "updated_at"])
|
||||
|
||||
shipment_models.Shipment.objects.create(
|
||||
merchant=self.merchant1,
|
||||
customer=self.customer1,
|
||||
shipment_date="2026-01-19",
|
||||
created_by=self.user1,
|
||||
remark="still-null",
|
||||
area="A4",
|
||||
)
|
||||
|
||||
resp = self.client.get("/api/v1/shipment/shipments/?delivery_isnull=false")
|
||||
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
data = resp.json()
|
||||
self.assertEqual(data["count"], 1)
|
||||
self.assertEqual(data["results"][0]["id"], self.shipment1.id)
|
||||
self.assertEqual(data["results"][0]["delivery_id"], delivery.id)
|
||||
|
||||
def test_retrieve_shipment_success(self):
|
||||
resp = self.client.get(f"/api/v1/shipment/shipments/{self.shipment1.id}/")
|
||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||
@@ -1714,6 +1835,8 @@ class ShipmentQueryAPITestCase(TestCase):
|
||||
self.assertIn("address", result)
|
||||
self.assertIn("contact_name", result)
|
||||
self.assertIn("contact_phone", result)
|
||||
self.assertIn("delivery_id", result)
|
||||
self.assertIsNone(result["delivery_id"])
|
||||
self.assertEqual(result["id"], self.shipment1.id)
|
||||
self.assertIn("area", result)
|
||||
self.assertEqual(result["area"], "A1")
|
||||
@@ -2664,6 +2787,9 @@ class ShipmentDeliveryAPITestCase(APITestCase):
|
||||
self.assertEqual(data["shipments_count"], 2)
|
||||
shipment_ids = [item["id"] for item in data["shipments"]]
|
||||
self.assertEqual(shipment_ids, [self.shipment1.id, self.shipment2.id])
|
||||
for item in data["shipments"]:
|
||||
self.assertIn("delivery_id", item)
|
||||
self.assertEqual(item["delivery_id"], delivery.id)
|
||||
|
||||
def test_patch_delivery_updates_fields_and_replaces_shipments(self):
|
||||
delivery = shipment_models.ShipmentDelivery.objects.create(
|
||||
|
||||
@@ -121,6 +121,8 @@ class ShipmentListCreateView(ListModelMixin, GenericAPIView):
|
||||
支持过滤参数(可选):
|
||||
- customer: 客户ID
|
||||
- status: 状态(1=草稿, 2=已发布, 3=已取消, 4=已驳回, 5=已审核)
|
||||
- delivery_id: 送货单ID(仅当传入具体ID时过滤;null/空值不触发过滤)
|
||||
- delivery_isnull: 是否仅查询未绑定/已绑定送货单的出货单(true/false)
|
||||
- external_id: 外部订单号(精确匹配)
|
||||
- shipment_date_from: 出货日期起始(YYYY-MM-DD)
|
||||
- shipment_date_to: 出货日期结束(YYYY-MM-DD,包含整天)
|
||||
@@ -151,6 +153,20 @@ class ShipmentListCreateView(ListModelMixin, GenericAPIView):
|
||||
if status_val:
|
||||
qs = qs.filter(status=status_val)
|
||||
|
||||
delivery_id = self.request.query_params.get("delivery_id")
|
||||
if delivery_id is not None:
|
||||
normalized_delivery_id = delivery_id.strip()
|
||||
if normalized_delivery_id and normalized_delivery_id.lower() not in {"null", "none"}:
|
||||
qs = qs.filter(delivery_id=normalized_delivery_id)
|
||||
|
||||
delivery_isnull = self.request.query_params.get("delivery_isnull")
|
||||
if delivery_isnull is not None:
|
||||
normalized_delivery_isnull = delivery_isnull.strip().lower()
|
||||
if normalized_delivery_isnull in {"1", "true", "yes"}:
|
||||
qs = qs.filter(delivery_id__isnull=True)
|
||||
elif normalized_delivery_isnull in {"0", "false", "no"}:
|
||||
qs = qs.filter(delivery_id__isnull=False)
|
||||
|
||||
external_id = self.request.query_params.get("external_id")
|
||||
if external_id:
|
||||
qs = qs.filter(external_id=external_id)
|
||||
@@ -1009,7 +1025,7 @@ class SalesItemDetailView(GenericAPIView):
|
||||
emp = getattr(user, "employee", None)
|
||||
merchant = getattr(emp, "merchant", None) if emp else None
|
||||
if not merchant:
|
||||
return SalesItem.objects.none()
|
||||
return qs.none()
|
||||
return qs.filter(merchant=merchant)
|
||||
|
||||
def get(self, request, pk: int):
|
||||
@@ -1116,7 +1132,7 @@ class SalesItemRebuildView(GenericAPIView):
|
||||
emp = getattr(user, "employee", None)
|
||||
merchant = getattr(emp, "merchant", None) if emp else None
|
||||
if not merchant:
|
||||
return SalesItem.objects.none()
|
||||
return qs.none()
|
||||
return qs.filter(merchant=merchant)
|
||||
|
||||
def post(self, request, pk: int):
|
||||
|
||||
Reference in New Issue
Block a user