forked from erp-dev/erp
feat: added sales_items and external_products into shipments list api
This commit is contained in:
@@ -3,7 +3,7 @@ Shipment API 序列化器
|
|||||||
"""
|
"""
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from shipment.models import Shipment
|
from shipment.models import ExternalFinishedProduct, SalesItem, Shipment
|
||||||
|
|
||||||
|
|
||||||
class ShipmentSerializer(serializers.ModelSerializer):
|
class ShipmentSerializer(serializers.ModelSerializer):
|
||||||
@@ -20,6 +20,8 @@ class ShipmentSerializer(serializers.ModelSerializer):
|
|||||||
external_finished_products_count = serializers.SerializerMethodField()
|
external_finished_products_count = serializers.SerializerMethodField()
|
||||||
merchant_id = serializers.IntegerField(source='merchant.id', read_only=True)
|
merchant_id = serializers.IntegerField(source='merchant.id', read_only=True)
|
||||||
merchant_name = serializers.CharField(source='merchant.name', read_only=True)
|
merchant_name = serializers.CharField(source='merchant.name', read_only=True)
|
||||||
|
sales_items = serializers.SerializerMethodField()
|
||||||
|
external_finished_products = serializers.SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Shipment
|
model = Shipment
|
||||||
@@ -31,6 +33,8 @@ class ShipmentSerializer(serializers.ModelSerializer):
|
|||||||
'cancelled_at', 'cancelled_by_id', 'cancelled_by_name',
|
'cancelled_at', 'cancelled_by_id', 'cancelled_by_name',
|
||||||
'items_count', 'created_by_id', 'created_by_name',
|
'items_count', 'created_by_id', 'created_by_name',
|
||||||
'external_finished_products_count',
|
'external_finished_products_count',
|
||||||
|
'sales_items',
|
||||||
|
'external_finished_products',
|
||||||
'created_at', 'updated_at'
|
'created_at', 'updated_at'
|
||||||
]
|
]
|
||||||
read_only_fields = ['id', 'created_at', 'updated_at']
|
read_only_fields = ['id', 'created_at', 'updated_at']
|
||||||
@@ -57,6 +61,51 @@ class ShipmentSerializer(serializers.ModelSerializer):
|
|||||||
def get_external_finished_products_count(self, obj):
|
def get_external_finished_products_count(self, obj):
|
||||||
return obj.external_finished_products.count()
|
return obj.external_finished_products.count()
|
||||||
|
|
||||||
|
def get_sales_items(self, obj):
|
||||||
|
"""
|
||||||
|
出货单关联的销售品明细(无则返回空数组)。
|
||||||
|
"""
|
||||||
|
# 优先使用 prefetch 的 related manager;兜底为 none()
|
||||||
|
rel = getattr(obj, 'items', None)
|
||||||
|
items = list(rel.all()) if rel is not None else []
|
||||||
|
return SalesItemSerializer(items, many=True).data
|
||||||
|
|
||||||
|
def get_external_finished_products(self, obj):
|
||||||
|
"""
|
||||||
|
出货单关联的外部成品表明细(无则返回空数组)。
|
||||||
|
"""
|
||||||
|
rel = getattr(obj, 'external_finished_products', None)
|
||||||
|
products = list(rel.all()) if rel is not None else []
|
||||||
|
return ExternalFinishedProductSerializer(products, many=True).data
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalFinishedProductSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
外部成品表序列化器(只读)
|
||||||
|
"""
|
||||||
|
created_by_id = serializers.IntegerField(source='created_by.id', read_only=True, allow_null=True)
|
||||||
|
created_by_name = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = ExternalFinishedProduct
|
||||||
|
fields = [
|
||||||
|
'id',
|
||||||
|
'style_name',
|
||||||
|
'num_of_rolls',
|
||||||
|
'created_at',
|
||||||
|
'created_by_id',
|
||||||
|
'created_by_name',
|
||||||
|
]
|
||||||
|
read_only_fields = fields
|
||||||
|
|
||||||
|
def get_created_by_name(self, obj):
|
||||||
|
if obj.created_by:
|
||||||
|
employee = getattr(obj.created_by, 'employee', None)
|
||||||
|
if employee:
|
||||||
|
return employee.name
|
||||||
|
return obj.created_by.username
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class ShipmentCreateNormalSerializer(serializers.Serializer):
|
class ShipmentCreateNormalSerializer(serializers.Serializer):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -638,10 +638,22 @@ class ShipmentQueryAPITestCase(TestCase):
|
|||||||
self.assertIn(self.shipment1.id, ids)
|
self.assertIn(self.shipment1.id, ids)
|
||||||
self.assertNotIn(self.shipment2.id, ids)
|
self.assertNotIn(self.shipment2.id, ids)
|
||||||
|
|
||||||
|
# 关键:列表序列化器必须稳定输出明细数组字段(即使为空)
|
||||||
|
item = next(it for it in data['results'] if it['id'] == self.shipment1.id)
|
||||||
|
self.assertIn('sales_items', item)
|
||||||
|
self.assertIsInstance(item['sales_items'], list)
|
||||||
|
self.assertIn('external_finished_products', item)
|
||||||
|
self.assertIsInstance(item['external_finished_products'], list)
|
||||||
|
|
||||||
def test_retrieve_shipment_success(self):
|
def test_retrieve_shipment_success(self):
|
||||||
resp = self.client.get(f'/api/v1/shipment/shipments/{self.shipment1.id}/')
|
resp = self.client.get(f'/api/v1/shipment/shipments/{self.shipment1.id}/')
|
||||||
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
self.assertEqual(resp.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(resp.json()['id'], self.shipment1.id)
|
result = resp.json()
|
||||||
|
self.assertEqual(result['id'], self.shipment1.id)
|
||||||
|
self.assertIn('sales_items', result)
|
||||||
|
self.assertIsInstance(result['sales_items'], list)
|
||||||
|
self.assertIn('external_finished_products', result)
|
||||||
|
self.assertIsInstance(result['external_finished_products'], list)
|
||||||
|
|
||||||
def test_retrieve_other_merchant_shipment_404(self):
|
def test_retrieve_other_merchant_shipment_404(self):
|
||||||
resp = self.client.get(f'/api/v1/shipment/shipments/{self.shipment2.id}/')
|
resp = self.client.get(f'/api/v1/shipment/shipments/{self.shipment2.id}/')
|
||||||
|
|||||||
@@ -40,5 +40,6 @@
|
|||||||
- 补齐出货单查询接口:
|
- 补齐出货单查询接口:
|
||||||
- `GET /api/v1/shipment/shipments/`:出货单列表(merchant 隔离 + LimitOffsetPagination)
|
- `GET /api/v1/shipment/shipments/`:出货单列表(merchant 隔离 + LimitOffsetPagination)
|
||||||
- `GET /api/v1/shipment/shipments/<id>/`:出货单详情(merchant 隔离)
|
- `GET /api/v1/shipment/shipments/<id>/`:出货单详情(merchant 隔离)
|
||||||
|
- 出货单列表序列化器增强:`sales_items` 与 `external_finished_products` 字段稳定输出(无数据也返回空数组)
|
||||||
- 更新 `docs/shipment_api.md`:补充“查询出货单(列表/详情)”说明
|
- 更新 `docs/shipment_api.md`:补充“查询出货单(列表/详情)”说明
|
||||||
- 新增 API 测试:覆盖列表/详情的商户隔离与未登录行为
|
- 新增 API 测试:覆盖列表/详情的商户隔离与未登录行为
|
||||||
|
|||||||
@@ -49,6 +49,8 @@
|
|||||||
"shipment_date": "2026-01-14",
|
"shipment_date": "2026-01-14",
|
||||||
"remark": "备注信息",
|
"remark": "备注信息",
|
||||||
"items_count": 3,
|
"items_count": 3,
|
||||||
|
"sales_items": [],
|
||||||
|
"external_finished_products": [],
|
||||||
"created_by_id": 1,
|
"created_by_id": 1,
|
||||||
"created_by_name": "张三",
|
"created_by_name": "张三",
|
||||||
"created_at": "2026-01-14T10:00:00Z",
|
"created_at": "2026-01-14T10:00:00Z",
|
||||||
@@ -108,6 +110,8 @@
|
|||||||
"shipment_date": "2026-01-14",
|
"shipment_date": "2026-01-14",
|
||||||
"remark": "备注信息",
|
"remark": "备注信息",
|
||||||
"items_count": 3,
|
"items_count": 3,
|
||||||
|
"sales_items": [],
|
||||||
|
"external_finished_products": [],
|
||||||
"created_by_id": 1,
|
"created_by_id": 1,
|
||||||
"created_by_name": "张三",
|
"created_by_name": "张三",
|
||||||
"created_at": "2026-01-14T10:00:00Z",
|
"created_at": "2026-01-14T10:00:00Z",
|
||||||
|
|||||||
Reference in New Issue
Block a user