1
0
forked from erp-dev/erp

fix: added delivery_id and delivery_isnull query param to shipment list api

This commit is contained in:
2026-04-21 23:09:58 +08:00
parent 8b1afc64c5
commit 35e6af1128
6 changed files with 160 additions and 2 deletions

View File

@@ -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

View File

@@ -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(

View File

@@ -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):