1
0
forked from erp-dev/erp

feat: added type field into warehouse model

This commit is contained in:
2025-11-20 15:44:35 +08:00
parent 6091280fdc
commit 2a838199fa
22 changed files with 1384 additions and 38 deletions

View File

@@ -201,7 +201,7 @@ class PrintingJobCreateUpdateSerializer(serializers.ModelSerializer):
class PlateOrderDesignCodeMixin:
"""确保 design_code 为空时使用主键补全"""
"""确保 design_code 为空时使用主键"""
@staticmethod
def _normalize_design_code(design_code: str | None, instance_id: int | None) -> str | None:
@@ -222,30 +222,50 @@ class PlateOrderListSerializer(PlateOrderDesignCodeMixin, serializers.ModelSeria
customer_name = serializers.CharField(source="customer.name", read_only=True)
salesperson_name = serializers.CharField(source="salesperson.name", read_only=True)
merchandiser_name = serializers.CharField(source="merchandiser.name", read_only=True)
designer_name = serializers.CharField(source="designer.name", read_only=True)
status = serializers.CharField(read_only=True)
progress_percentage = serializers.IntegerField(read_only=True)
process_name = serializers.SerializerMethodField()
plate_image_url = serializers.SerializerMethodField()
class Meta:
model = models.PlateOrder
fields = [
'id', 'design_code', 'plate_type', 'plate_date',
'id', 'design_code', 'plate_type', 'plate_date', 'plate_method',
'plate_image', 'plate_image_url', 'plate_notes', 'reprint_reason',
'urgency_level', 'is_invalid',
'customer', 'customer_name', 'area',
'customer', 'customer_name', 'area', 'default_address',
'salesperson', 'salesperson_name',
'merchandiser', 'merchandiser_name',
'style_name', 'fabric', 'fabric_source', 'width',
'designer', 'designer_name',
'style_name', 'fabric', 'fabric_source', 'width', 'production_method',
'is_mark_frame', 'drawing_rating', 'color_matching_rating',
'sample_rating', 'difficulty_rating',
'sample_meter', 'required_sample_meters',
'required_completion_date', 'completion_date',
'is_ordered', 'process', 'process_name', 'business_object_id',
'status', 'progress_percentage',
'approval_result', 'is_ordered', 'customer_feedback',
'process', 'process_name',
'status', 'status_id', 'is_completed', 'has_started',
'progress_percentage', 'business_object_id',
'created_at', 'updated_at'
]
read_only_fields = [
'id', 'status', 'progress_percentage',
'created_at', 'updated_at'
]
def get_plate_image_url(self, obj):
"""获取图片完整URL"""
if obj.plate_image:
request = self.context.get("request")
if request:
return request.build_absolute_uri(obj.plate_image.url)
return obj.plate_image.url
return None
def get_process_name(self, obj):
def get_process_name(self, obj) ->str | None:
"""获取流程名称"""
if obj.process:
try:
@@ -263,6 +283,7 @@ class PlateOrderDetailSerializer(PlateOrderDesignCodeMixin, serializers.ModelSer
customer_phone = serializers.CharField(source="customer.mobile", read_only=True)
salesperson_name = serializers.CharField(source="salesperson.name", read_only=True)
merchandiser_name = serializers.CharField(source="merchandiser.name", read_only=True)
designer_name = serializers.CharField(source="designer.name", read_only=True)
status = serializers.CharField(read_only=True)
status_id = serializers.IntegerField(read_only=True)
is_completed = serializers.BooleanField(read_only=True)
@@ -281,6 +302,7 @@ class PlateOrderDetailSerializer(PlateOrderDesignCodeMixin, serializers.ModelSer
'customer', 'customer_name', 'customer_phone', 'area', 'default_address',
'salesperson', 'salesperson_name',
'merchandiser', 'merchandiser_name',
'designer', 'designer_name',
'style_name', 'fabric', 'fabric_source', 'width', 'production_method',
'is_mark_frame', 'drawing_rating', 'color_matching_rating',
'sample_rating', 'difficulty_rating',
@@ -333,7 +355,7 @@ class PlateOrderCreateUpdateSerializer(serializers.ModelSerializer):
"plate_image", "plate_notes", "reprint_reason",
"urgency_level", "is_invalid",
"customer", "area", "default_address",
"salesperson", "merchandiser",
"salesperson", "merchandiser", "designer",
"style_name", "fabric", "fabric_source", "width", "production_method",
"is_mark_frame", "drawing_rating", "color_matching_rating",
"sample_rating", "difficulty_rating",
@@ -362,6 +384,12 @@ class PlateOrderCreateUpdateSerializer(serializers.ModelSerializer):
raise serializers.ValidationError("跟单员不存在")
return value
def validate_designer(self, value):
"""验证设计师是否存在"""
if value and not Employee.objects.filter(id=value.id).exists():
raise serializers.ValidationError("设计师不存在")
return value
def validate_sample_meter(self, value):
"""验证样品米数"""
if value is not None and value < 0:

View File

@@ -39,7 +39,6 @@ class PrintingOrderAPITestCase(TestCase):
merchant=self.merchant,
name='测试员工',
mobile='13800138000',
job_type=basic_models.EmployeeTypeEnum.PRINTER,
status=basic_models.EmployeeStatusEnum.ACTIVE
)

View File

@@ -38,7 +38,6 @@ class PlateOrderAPITestCase(TestCase):
merchant=self.merchant,
name='测试员工',
mobile='13800138000',
job_type=basic_models.EmployeeTypeEnum.PRINTER,
status=basic_models.EmployeeStatusEnum.ACTIVE
)
@@ -47,7 +46,6 @@ class PlateOrderAPITestCase(TestCase):
merchant=self.merchant,
name='测试业务员',
mobile='13800138001',
job_type=basic_models.EmployeeTypeEnum.PRINTER,
status=basic_models.EmployeeStatusEnum.ACTIVE
)
@@ -56,7 +54,14 @@ class PlateOrderAPITestCase(TestCase):
merchant=self.merchant,
name='测试跟单员',
mobile='13800138002',
job_type=basic_models.EmployeeTypeEnum.WARE,
status=basic_models.EmployeeStatusEnum.ACTIVE
)
# 创建设计师
self.designer = basic_models.Employee.objects.create(
merchant=self.merchant,
name='测试设计师',
mobile='13800138003',
status=basic_models.EmployeeStatusEnum.ACTIVE
)
@@ -189,7 +194,7 @@ class PlateOrderAPITestCase(TestCase):
self.assertIn('salesperson_name', response.data)
def test_design_code_fallback_in_detail(self):
"""design_code 为空时返回补零后的主键"""
"""design_code 为空时返回主键ID"""
plate_order = printing_models.PlateOrder.objects.create(
customer=self.customer,
design_code='',
@@ -200,10 +205,10 @@ class PlateOrderAPITestCase(TestCase):
response = self.client.get(f'/api/v1/plate-orders/{plate_order.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['design_code'], f'{plate_order.id:06d}')
self.assertEqual(response.data['design_code'], str(plate_order.id))
def test_design_code_fallback_in_list(self):
"""列表接口也应返回补零后的设计编号"""
"""列表接口也应返回主键ID"""
printing_models.PlateOrder.objects.create(
customer=self.customer,
design_code='',
@@ -215,7 +220,7 @@ class PlateOrderAPITestCase(TestCase):
response = self.client.get('/api/v1/plate-orders/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
results = response.data['results'] if isinstance(response.data, dict) else response.data
self.assertEqual(results[0]['design_code'], f"{results[0]['id']:06d}")
self.assertEqual(results[0]['design_code'], str(results[0]['id']))
def test_fabric_source_field_in_detail(self):
"""验证布料来源字段在返回中存在"""
@@ -777,7 +782,22 @@ class PlateOrderInvalidateAPITestCase(TestCase):
merchant=self.merchant,
name='测试员工',
mobile='13800138000',
job_type=basic_models.EmployeeTypeEnum.PRINTER,
status=basic_models.EmployeeStatusEnum.ACTIVE
)
# 创建业务员
self.salesperson = basic_models.Employee.objects.create(
merchant=self.merchant,
name='测试业务员',
mobile='13800138001',
status=basic_models.EmployeeStatusEnum.ACTIVE
)
# 创建设计师
self.designer = basic_models.Employee.objects.create(
merchant=self.merchant,
name='测试设计师',
mobile='13800138003',
status=basic_models.EmployeeStatusEnum.ACTIVE
)
@@ -917,3 +937,101 @@ class PlateOrderInvalidateAPITestCase(TestCase):
response = self.client.post(f'/api/v1/plate-orders/{plate_order.id}/activate/')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('未作废', response.data['detail'])
def test_create_plate_order_with_designer(self):
"""测试创建带设计师的开版订单"""
data = {
'customer': self.customer.id,
'design_code': 'DESIGN_DESIGNER_001',
'plate_type': '圆网',
'style_name': '测试款式-设计师',
'fabric': '棉布',
'width': '150cm',
'designer': self.designer.id,
'salesperson': self.salesperson.id,
}
response = self.client.post('/api/v1/plate-orders/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertEqual(response.data['designer'], self.designer.id)
# 验证数据库中创建了记录并关联了设计师
plate_order = printing_models.PlateOrder.objects.get(id=response.data['id'])
self.assertEqual(plate_order.designer, self.designer)
def test_create_plate_order_without_designer(self):
"""测试创建不带设计师的开版订单"""
data = {
'customer': self.customer.id,
'design_code': 'DESIGN_NO_DESIGNER',
'plate_type': '圆网',
'style_name': '测试款式-无设计师',
'fabric': '棉布',
'width': '150cm',
}
response = self.client.post('/api/v1/plate-orders/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
self.assertIsNone(response.data['designer'])
def test_update_plate_order_designer(self):
"""测试更新开版订单的设计师"""
plate_order = printing_models.PlateOrder.objects.create(
customer=self.customer,
design_code='DESIGN_UPDATE_DESIGNER',
plate_type='圆网',
style_name='测试款式',
fabric='棉布',
)
data = {
'designer': self.designer.id,
}
response = self.client.patch(f'/api/v1/plate-orders/{plate_order.id}/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['designer'], self.designer.id)
# 验证数据库已更新
plate_order.refresh_from_db()
self.assertEqual(plate_order.designer, self.designer)
def test_list_plate_orders_includes_designer_name(self):
"""测试列表接口包含设计师字段"""
printing_models.PlateOrder.objects.create(
customer=self.customer,
design_code='DESIGN_LIST_1',
plate_type='圆网',
style_name='测试款式1',
designer=self.designer,
)
printing_models.PlateOrder.objects.create(
customer=self.customer,
design_code='DESIGN_LIST_2',
plate_type='平网',
style_name='测试款式2',
)
response = self.client.get('/api/v1/plate-orders/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
results = response.data['results'] if isinstance(response.data, dict) else response.data
# 验证数据包含designer字段designer_name在designer为空时可能不存在
self.assertIn('designer', results[0])
def test_retrieve_plate_order_includes_designer(self):
"""测试详情接口包含设计师信息"""
plate_order = printing_models.PlateOrder.objects.create(
customer=self.customer,
design_code='DESIGN_RETRIEVE',
plate_type='圆网',
style_name='测试款式',
designer=self.designer,
salesperson=self.salesperson,
)
response = self.client.get(f'/api/v1/plate-orders/{plate_order.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['designer'], self.designer.id)
self.assertIn('designer_name', response.data)

View File

@@ -39,7 +39,6 @@ class PlateOrderFileUploadTestCase(TestCase):
merchant=self.merchant,
name='测试员工',
mobile='13800138000',
job_type=basic_models.EmployeeTypeEnum.PRINTER,
status=basic_models.EmployeeStatusEnum.ACTIVE
)

View File

@@ -39,7 +39,6 @@ class PrintingJobAPITestCase(TestCase):
merchant=self.merchant,
name='测试员工',
mobile='13800138000',
job_type=basic_models.EmployeeTypeEnum.PRINTER,
status=basic_models.EmployeeStatusEnum.ACTIVE
)
@@ -979,9 +978,9 @@ class PrintingJobAPITestCase(TestCase):
self.assertIsNotNone(timeline[0]['completed_at'])
self.assertIsNotNone(timeline[0]['completed_by'])
# 第二个状态应该是进行中
# 第二个状态应该是未开始(因为我们废除了 in_progress 的概念)
self.assertEqual(timeline[1]['state_name'], self.state2.name)
self.assertEqual(timeline[1]['status'], 'in_progress')
self.assertEqual(timeline[1]['status'], 'not_started')
self.assertIsNone(timeline[1]['completed_at'])
# 第三个状态应该是未开始

View File

@@ -399,7 +399,7 @@ class PrintingJobServiceTest(TestCase):
self.assertIn('is_completed', status_info)
self.assertIn('has_started', status_info)
# 未开始状态
self.assertEqual(status_info['status'], '未开始')
# 未开始状态(现在返回空字符串或第一个状态名称)
self.assertIn(status_info['status'], ['', self.state1.name])
self.assertFalse(status_info['is_completed'])
self.assertFalse(status_info['has_started'])