1
0
forked from erp-dev/erp
Files
erpnew/api_v1/views/printing/test_printing_job_api.py

1350 lines
51 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
PrintingJob API 测试
"""
from django.test import TestCase
from django.conf import settings
from rest_framework.test import APIClient
from rest_framework import status
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from basic_info import models as basic_models
from printing import models as printing_models
from shipment import models as shipment_models
from stateflow import models as stateflow_models
User = get_user_model()
class PrintingJobAPITestCase(TestCase):
"""测试 PrintingJob API"""
def setUp(self):
self.client = APIClient()
# 创建商户
self.merchant = basic_models.Merchant.objects.create(
name='测试印花厂',
type=basic_models.MerchantTypeEnum.FACTORY
)
# 创建用户
self.user = User.objects.create_user(
username='testuser',
password='testpass123',
email='test@example.com'
)
# PrintingJobViewSet 默认做 merchant 隔离;本测试集不关注该隔离逻辑,
# 设为 superuser 以避免因测试数据未设置 merchant 导致的 404/列表为空。
self.user.is_superuser = True
self.user.save(update_fields=['is_superuser'])
# 创建员工并关联商户
self.employee = basic_models.Employee.objects.create(
sys_user=self.user,
merchant=self.merchant,
name='测试员工',
mobile='13800138000',
status=basic_models.EmployeeStatusEnum.ACTIVE
)
# 创建客户
self.customer = basic_models.Customer.objects.create(
merchant=self.merchant,
name='测试客户',
mobile='13900139000',
area='测试地区'
)
# 创建流程
self.state1 = stateflow_models.State.objects.create(name='待印染')
self.state2 = stateflow_models.State.objects.create(name='印染中')
self.state3 = stateflow_models.State.objects.create(name='已完成')
self.process = stateflow_models.Process.objects.create(name='印染流程')
self.process.replace_nodes([self.state1, self.state2, self.state3])
# 设置默认流程
settings.PRINTING_DEFAULT_PROCESS_ID = self.process.id
# 创建印染订单
self.printing_order = printing_models.PrintingOrder.objects.create(
customer=self.customer,
fabric='测试布料',
width='150cm',
process=self.process,
)
# 创建产品类别
self.category = basic_models.ProductCategory.objects.create(
merchant=self.merchant,
name='测试类别'
)
# 创建产品
self.product = basic_models.Product.objects.create(
merchant=self.merchant,
category=self.category,
name='测试产品',
human_id='TEST001',
unit=basic_models.ProductUnitEnum.METER
)
# 认证用户
self.client.force_authenticate(user=self.user)
# 给用户添加基础权限
view_perm = Permission.objects.get(codename='view_printingjob')
add_perm = Permission.objects.get(codename='add_printingjob')
change_perm = Permission.objects.get(codename='change_printingjob')
self.user.user_permissions.add(view_perm, add_perm, change_perm)
def test_create_printing_job(self):
"""测试创建印染款式明细"""
data = {
'printing_order': self.printing_order.id,
'product': self.product.id,
'quantity': 100,
'unit': '',
'size': '50*60',
'pieces': 10,
'description': '测试备注'
}
response = self.client.post('/api/v1/printing-jobs/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# 新增字段:批量推进记录(稳定输出 key默认空数组
self.assertIn('batch_advance_records', response.data)
self.assertIsInstance(response.data['batch_advance_records'], list)
self.assertEqual(len(response.data['batch_advance_records']), 0)
# 验证创建成功
job = printing_models.PrintingJob.objects.filter(
printing_order=self.printing_order,
product=self.product
).first()
self.assertIsNotNone(job)
self.assertEqual(job.quantity, 100)
self.assertEqual(job.unit, '')
self.assertEqual(job.pieces, 10)
def test_list_printing_jobs(self):
"""测试获取款式明细列表"""
# 创建测试数据
job1 = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
job2 = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=200,
unit='',
size='60*70',
pieces=20
)
response = self.client.get('/api/v1/printing-jobs/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 2)
# 新增字段:批量推进记录(稳定输出 key
for item in response.data['results']:
self.assertIn('batch_advance_records', item)
self.assertIsInstance(item['batch_advance_records'], list)
self.assertEqual(len(item['batch_advance_records']), 0)
def test_retrieve_printing_job(self):
"""测试获取款式明细详情"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10,
description='详情测试'
)
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['quantity'], 100)
self.assertEqual(response.data['unit'], '')
self.assertIn('product_name', response.data)
self.assertEqual(response.data['product_name'], self.product.name)
# 新增字段:批量推进记录(稳定输出 key
self.assertIn('batch_advance_records', response.data)
self.assertIsInstance(response.data['batch_advance_records'], list)
self.assertEqual(len(response.data['batch_advance_records']), 0)
def test_batch_advance_records_in_list_and_detail(self):
"""测试 PrintingJob 序列化输出包含批量推进记录(有记录时返回明细,无记录返回空)"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10,
description='批量记录测试'
)
# 创建一条批量推进记录并关联该 job
record = printing_models.PrintingJobBatchAdvanceRecord.objects.create(
printing_order=self.printing_order,
state=self.state1,
created_by=self.user,
parameters={'temperature': '25.5', 'operator': '张三'},
)
record.printing_jobs.add(job)
# list: 应包含 batch_advance_records
response = self.client.get('/api/v1/printing-jobs/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
target = next(item for item in response.data['results'] if item['id'] == job.id)
self.assertIn('batch_advance_records', target)
self.assertEqual(len(target['batch_advance_records']), 1)
rec = target['batch_advance_records'][0]
self.assertEqual(rec['id'], record.id)
self.assertEqual(rec['printing_order'], self.printing_order.id)
self.assertEqual(rec['state'], self.state1.id)
self.assertEqual(rec['state_id'], self.state1.id)
self.assertEqual(rec['state_name'], self.state1.name)
self.assertEqual(rec['created_by'], self.user.id)
self.assertEqual(rec['created_by_username'], self.user.username)
self.assertEqual(rec['created_by_name'], self.employee.name)
self.assertEqual(rec['parameters']['temperature'], '25.5')
self.assertEqual(rec['parameters']['operator'], '张三')
# detail: 同样应包含 batch_advance_records
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('batch_advance_records', response.data)
self.assertEqual(len(response.data['batch_advance_records']), 1)
def test_list_printing_jobs_includes_saleitems(self):
"""测试 list 返回包含关联的 shipment.SalesItem 数组"""
job1 = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
job2 = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=200,
unit='',
size='60*70',
pieces=20
)
item1 = shipment_models.SalesItem.objects.create(
merchant=self.merchant,
name='销售品A',
quantity='88.50',
unit=shipment_models.UnitChoices.METER,
created_by=self.user,
printing_job_id=job1.id,
customer_id=self.customer.id,
position='A1-01',
remark='备注A',
)
item2 = shipment_models.SalesItem.objects.create(
merchant=self.merchant,
name='销售品B',
quantity='12.00',
unit=shipment_models.UnitChoices.PIECE,
created_by=self.user,
printing_job_id=job1.id,
customer_id=self.customer.id,
position='A1-02',
remark='备注B',
)
response = self.client.get(f'/api/v1/printing-jobs/?printing_order={self.printing_order.id}')
self.assertEqual(response.status_code, status.HTTP_200_OK)
data_by_id = {item['id']: item for item in response.data['results']}
self.assertIn('saleitems', data_by_id[job1.id])
self.assertEqual(len(data_by_id[job1.id]['saleitems']), 2)
self.assertEqual([item['id'] for item in data_by_id[job1.id]['saleitems']], [item1.id, item2.id])
self.assertEqual(data_by_id[job1.id]['saleitems'][0]['printing_job_id'], job1.id)
self.assertEqual(data_by_id[job1.id]['saleitems'][0]['name'], '销售品A')
self.assertIn('saleitems', data_by_id[job2.id])
self.assertEqual(data_by_id[job2.id]['saleitems'], [])
def test_retrieve_printing_job_includes_saleitems(self):
"""测试 detail 返回包含关联的 shipment.SalesItem 数组"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
shipment_models.SalesItem.objects.create(
merchant=self.merchant,
name='详情销售品',
quantity='66.00',
unit=shipment_models.UnitChoices.METER,
created_by=self.user,
printing_job_id=job.id,
customer_id=self.customer.id,
position='B2-01',
remark='详情备注',
)
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('saleitems', response.data)
self.assertEqual(len(response.data['saleitems']), 1)
self.assertEqual(response.data['saleitems'][0]['printing_job_id'], job.id)
self.assertEqual(response.data['saleitems'][0]['name'], '详情销售品')
def test_update_printing_job(self):
"""测试更新款式明细"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
update_data = {
'printing_order': self.printing_order.id,
'product': self.product.id,
'quantity': 200,
'unit': '',
'size': '60*70',
'pieces': 20,
'description': '更新后的备注'
}
response = self.client.put(
f'/api/v1/printing-jobs/{job.id}/',
update_data,
format='json'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
job.refresh_from_db()
self.assertEqual(job.quantity, 200)
self.assertEqual(job.unit, '')
self.assertEqual(job.pieces, 20)
def test_cannot_change_printing_order_on_update(self):
"""测试:不允许通过更新接口修改 PrintingJob.printing_order"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
)
other_order = printing_models.PrintingOrder.objects.create(
customer=self.customer,
fabric='其他布料',
width='160cm',
)
update_data = {
'printing_order': other_order.id, # 尝试变更绑定关系
'product': self.product.id,
'quantity': 200,
'unit': '',
}
response = self.client.put(
f'/api/v1/printing-jobs/{job.id}/',
update_data,
format='json'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('printing_order', response.data)
def test_partial_update_printing_job(self):
"""测试部分更新款式明细"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
patch_data = {
'quantity': 150,
'pieces': 15
}
response = self.client.patch(
f'/api/v1/printing-jobs/{job.id}/',
patch_data,
format='json'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
job.refresh_from_db()
self.assertEqual(job.quantity, 150)
self.assertEqual(job.pieces, 15)
self.assertEqual(job.unit, '') # 未修改字段保持不变
def test_delete_printing_job_forbidden(self):
"""测试删除款式明细被禁用"""
# 添加删除权限以便测试destroy方法的自定义逻辑
delete_perm = Permission.objects.get(codename='delete_printingjob')
self.user.user_permissions.add(delete_perm)
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
response = self.client.delete(f'/api/v1/printing-jobs/{job.id}/')
self.assertEqual(response.status_code, status.HTTP_405_METHOD_NOT_ALLOWED)
self.assertIn('不支持删除', response.data['detail'])
# 验证明细仍然存在
self.assertTrue(
printing_models.PrintingJob.objects.filter(id=job.id).exists()
)
def test_filter_by_printing_order(self):
"""测试按印染订单过滤"""
order2 = printing_models.PrintingOrder.objects.create(
customer=self.customer,
fabric='其他布料',
width='160cm'
)
printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
printing_models.PrintingJob.objects.create(
printing_order=order2,
product=self.product,
quantity=200,
unit='',
size='60*70',
pieces=20
)
response = self.client.get(f'/api/v1/printing-jobs/?printing_order={self.printing_order.id}')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
self.assertEqual(response.data['results'][0]['printing_order'], self.printing_order.id)
def test_filter_by_product(self):
"""测试按产品过滤"""
product2 = basic_models.Product.objects.create(
merchant=self.merchant,
category=self.category,
name='产品2',
human_id='TEST002',
unit=basic_models.ProductUnitEnum.METER
)
printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=product2,
quantity=200,
unit='',
size='60*70',
pieces=20
)
response = self.client.get(f'/api/v1/printing-jobs/?product={self.product.id}')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
self.assertEqual(response.data['results'][0]['product'], self.product.id)
def test_filter_by_quantity_range(self):
"""测试按数量范围过滤"""
printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=50,
unit='',
size='50*60',
pieces=10
)
printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=150,
unit='',
size='60*70',
pieces=20
)
printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=250,
unit='',
size='70*80',
pieces=30
)
response = self.client.get('/api/v1/printing-jobs/?quantity_min=100&quantity_max=200')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
self.assertEqual(response.data['results'][0]['quantity'], 150)
def test_search_by_product_name(self):
"""测试按产品名称搜索"""
product2 = basic_models.Product.objects.create(
merchant=self.merchant,
category=self.category,
name='特殊产品',
human_id='TEST003',
unit=basic_models.ProductUnitEnum.METER
)
printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=product2,
quantity=200,
unit='',
size='60*70',
pieces=20
)
response = self.client.get('/api/v1/printing-jobs/?search=特殊')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
self.assertIn('特殊', response.data['results'][0]['product_name'])
def test_ordering(self):
"""测试排序"""
job1 = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
job2 = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=200,
unit='',
size='60*70',
pieces=20
)
# 按数量升序
response = self.client.get('/api/v1/printing-jobs/?ordering=quantity')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['results'][0]['quantity'], 100)
self.assertEqual(response.data['results'][1]['quantity'], 200)
# 按数量降序
response = self.client.get('/api/v1/printing-jobs/?ordering=-quantity')
self.assertEqual(response.data['results'][0]['quantity'], 200)
self.assertEqual(response.data['results'][1]['quantity'], 100)
def test_validate_quantity_positive(self):
"""测试数量必须大于0"""
data = {
'printing_order': self.printing_order.id,
'product': self.product.id,
'quantity': 0, # 无效数量
'unit': '',
'size': '50*60',
'pieces': 10
}
response = self.client.post('/api/v1/printing-jobs/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('quantity', response.data)
def test_validate_pieces_can_be_zero(self):
"""测试件数可以为0或null"""
data = {
'printing_order': self.printing_order.id,
'product': self.product.id,
'quantity': 100,
'unit': '',
'size': '50*60',
'pieces': 0 # 允许为0
}
response = self.client.post('/api/v1/printing-jobs/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# 验证创建成功
job = printing_models.PrintingJob.objects.get(id=response.data['id'])
self.assertEqual(job.pieces, 0)
def test_create_job_with_business_object(self):
"""测试创建任务自动创建 BusinessObject"""
data = {
'printing_order': self.printing_order.id,
'product': self.product.id,
'quantity': 100,
'unit': '',
'size': '50*60',
'pieces': 10
}
response = self.client.post('/api/v1/printing-jobs/', data, format='json')
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
# 验证 BusinessObject 已创建
job = printing_models.PrintingJob.objects.get(id=response.data['id'])
self.assertIsNotNone(job.business_object)
self.assertEqual(job.business_object.process, self.process)
# 新逻辑BusinessObject 绑定回 PrintingJob
self.assertEqual(job.business_object.content_type_id, ContentType.objects.get_for_model(printing_models.PrintingJob).id)
self.assertEqual(job.business_object.object_id, job.id)
def test_job_status_in_detail(self):
"""测试任务详情包含状态字段"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 验证状态字段
self.assertIn('status', response.data)
self.assertIn('status_id', response.data)
self.assertIn('is_completed', response.data)
self.assertIn('has_started', response.data)
self.assertIn('business_object_id', response.data)
# 未推进状态,但有 BusinessObject当前是初始状态
self.assertIn(response.data['status'], ['待印染', '未开始']) # 初始状态或未开始
self.assertFalse(response.data['is_completed'])
self.assertFalse(response.data['has_started'])
def test_job_status_in_list(self):
"""测试任务列表包含状态字段"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
response = self.client.get('/api/v1/printing-jobs/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 验证列表中的状态字段
self.assertIn('status', response.data['results'][0])
self.assertIn('is_completed', response.data['results'][0])
def test_job_completion_status(self):
"""测试任务完成状态判断"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
# 推进到最后一个状态流程有3个节点需要推进3次
from stateflow.services import advance_to_next_state
advance_to_next_state(business_object, self.user) # 完成第1个状态
advance_to_next_state(business_object, self.user) # 完成第2个状态
advance_to_next_state(business_object, self.user) # 完成第3个状态
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 验证完成状态
self.assertEqual(response.data['status'], '已完成')
self.assertTrue(response.data['is_completed'])
self.assertTrue(response.data['has_started'])
def test_advance_to_next_state(self):
"""测试推进到下一个状态 API"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
# 推进到下一个状态
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('detail', response.data)
self.assertIn('data', response.data)
self.assertIn('已完成状态', response.data['detail'])
# 验证状态已更新
job.refresh_from_db()
self.assertTrue(job.has_started)
self.assertEqual(job.status, self.state2.name) # 应该是第二个状态(当前进行中的)
def test_advance_to_next_state_without_business_object(self):
"""测试推进到下一个状态 - 没有 BusinessObject 的情况"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('没有关联的流程实例', response.data['detail'])
def test_advance_with_parameters(self):
"""测试推进到下一个状态 - 带参数"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
# 推进到下一个状态,带参数
data = {
'parameters': {
'temperature': '25.5',
'operator': '张三',
'humidity': '60%'
}
}
response = self.client.post(
f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/',
data,
format='json'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('detail', response.data)
self.assertIn('data', response.data)
# 验证参数已保存
job.refresh_from_db()
state_log = business_object.state_logs.filter(state=self.state1, is_cancelled=False).first()
self.assertIsNotNone(state_log)
# 验证参数记录
param_record = state_log.parameter_records.first()
self.assertIsNotNone(param_record)
self.assertEqual(param_record.parameters['temperature'], '25.5')
self.assertEqual(param_record.parameters['operator'], '张三')
self.assertEqual(param_record.parameters['humidity'], '60%')
def test_advance_with_required_parameters_missing(self):
"""测试推进到下一个状态 - 缺少必填参数"""
# 创建带必填参数的状态
param1 = stateflow_models.StateParameter.objects.create(
key='temperature',
is_required=True,
description='温度'
)
param2 = stateflow_models.StateParameter.objects.create(
key='operator',
is_required=True,
description='操作员'
)
state_with_params = stateflow_models.State.objects.create(name='带参数状态')
state_with_params.parameters.add(param1, param2)
# 创建新流程,第一个状态是带必填参数的
process_with_params = stateflow_models.Process.objects.create(name='带参数流程')
process_with_params.replace_nodes([state_with_params, self.state2])
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=process_with_params,
)
job.business_object = business_object
job.save()
# 尝试推进但不提供必填参数
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('缺失必填参数', response.data['detail'])
self.assertIn('temperature', response.data['detail'])
self.assertIn('operator', response.data['detail'])
def test_advance_with_partial_required_parameters(self):
"""测试推进到下一个状态 - 只提供部分必填参数"""
# 创建带必填参数的状态
param1 = stateflow_models.StateParameter.objects.create(
key='temperature',
is_required=True,
description='温度'
)
param2 = stateflow_models.StateParameter.objects.create(
key='operator',
is_required=True,
description='操作员'
)
state_with_params = stateflow_models.State.objects.create(name='带参数状态')
state_with_params.parameters.add(param1, param2)
# 创建新流程
process_with_params = stateflow_models.Process.objects.create(name='带参数流程')
process_with_params.replace_nodes([state_with_params, self.state2])
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=process_with_params,
)
job.business_object = business_object
job.save()
# 只提供一个必填参数
data = {
'parameters': {
'temperature': '25.5'
}
}
response = self.client.post(
f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/',
data,
format='json'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('缺失必填参数', response.data['detail'])
self.assertIn('operator', response.data['detail'])
self.assertNotIn('temperature', response.data['detail']) # temperature 已提供
def test_advance_through_all_states(self):
"""测试推进到完成所有状态"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
# 推进 3 次完成所有状态
for i in range(3):
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/advance-to-next-state/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 验证已完成
job.refresh_from_db()
self.assertTrue(job.is_completed)
self.assertEqual(job.status, '已完成')
def test_step_back_one_state(self):
"""测试回退一个状态 API"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
# 先推进两步
from stateflow.services import advance_to_next_state
advance_to_next_state(business_object, self.user)
advance_to_next_state(business_object, self.user)
# 回退一步
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/step-back-one-state/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('detail', response.data)
self.assertIn('data', response.data)
self.assertIn('已回退状态', response.data['detail'])
# 验证状态已回退
job.refresh_from_db()
self.assertEqual(job.status, self.state2.name) # 应该回退到第二个状态(当前)
def test_step_back_without_business_object(self):
"""测试回退状态 - 没有 BusinessObject 的情况"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/step-back-one-state/')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('没有关联的流程实例', response.data['detail'])
def test_step_back_without_records(self):
"""测试回退状态 - 没有状态流转记录的情况"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject 但不推进状态
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
response = self.client.post(f'/api/v1/printing-jobs/{job.id}/step-back-one-state/')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertIn('没有任何状态流转记录', response.data['detail'])
def test_completed_states_list(self):
"""测试获取已完成状态列表 API"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
# 推进两个状态
from stateflow.services import advance_to_next_state
advance_to_next_state(business_object, self.user)
advance_to_next_state(business_object, self.user)
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('count', response.data)
self.assertIn('results', response.data)
self.assertEqual(response.data['count'], 2)
self.assertEqual(len(response.data['results']), 2)
# 验证返回的状态数据
first_state = response.data['results'][0]
self.assertIn('state_id', first_state)
self.assertIn('state_name', first_state)
self.assertIn('completed_at', first_state)
self.assertIn('completed_by', first_state)
self.assertIn('is_cancelled', first_state)
self.assertFalse(first_state['is_cancelled'])
def test_completed_states_include_cancelled(self):
"""测试获取已完成状态列表 - 包含已撤销的记录"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
# 推进两个状态
from stateflow.services import advance_to_next_state, step_back_one_state
advance_to_next_state(business_object, self.user)
advance_to_next_state(business_object, self.user)
# 回退一次(会将最后一条记录标记为 cancelled
step_back_one_state(business_object, self.user)
# 不包含已撤销的记录(默认)
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
# 包含已撤销的记录
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/?include_cancelled=true')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 2)
# 验证有一条记录是已撤销的
cancelled_records = [r for r in response.data['results'] if r['is_cancelled']]
self.assertEqual(len(cancelled_records), 1)
def test_completed_states_empty(self):
"""测试获取已完成状态列表 - 没有完成任何状态"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject 但不推进状态
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/completed-states/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 0)
self.assertEqual(len(response.data['results']), 0)
def test_timeline(self):
"""测试获取流程时间线 API"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
# 推进一个状态
from stateflow.services import advance_to_next_state
advance_to_next_state(business_object, self.user)
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn('count', response.data)
self.assertIn('results', response.data)
self.assertEqual(response.data['count'], 3) # 流程有 3 个状态
self.assertEqual(len(response.data['results']), 3)
# 验证时间线数据
timeline = response.data['results']
# 第一个状态应该是已完成
self.assertEqual(timeline[0]['state_name'], self.state1.name)
self.assertEqual(timeline[0]['status'], 'completed')
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'], 'not_started')
self.assertIsNone(timeline[1]['completed_at'])
# 第三个状态应该是未开始
self.assertEqual(timeline[2]['state_name'], self.state3.name)
self.assertEqual(timeline[2]['status'], 'not_started')
self.assertIsNone(timeline[2]['completed_at'])
def test_timeline_all_completed(self):
"""测试获取流程时间线 - 所有状态已完成"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
# 完成所有状态
from stateflow.services import advance_to_next_state
advance_to_next_state(business_object, self.user)
advance_to_next_state(business_object, self.user)
advance_to_next_state(business_object, self.user)
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 验证所有状态都是已完成
timeline = response.data['results']
for item in timeline:
self.assertEqual(item['status'], 'completed')
self.assertIsNotNone(item['completed_at'])
def test_timeline_not_started(self):
"""测试获取流程时间线 - 未开始任何状态"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject 但不推进状态
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 验证第一个状态是未开始,因为没有推进任何状态
timeline = response.data['results']
self.assertEqual(timeline[0]['status'], 'not_started')
self.assertEqual(timeline[1]['status'], 'not_started')
self.assertEqual(timeline[2]['status'], 'not_started')
def test_timeline_excludes_cancelled(self):
"""测试获取流程时间线 - 自动过滤已撤销的记录"""
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
# 创建 BusinessObject
business_object = stateflow_models.BusinessObject.objects.create(
name=f'PrintingJob-{job.id}',
process=self.process,
)
job.business_object = business_object
job.save()
# 推进两个状态然后回退一次
from stateflow.services import advance_to_next_state, step_back_one_state
advance_to_next_state(business_object, self.user)
advance_to_next_state(business_object, self.user)
step_back_one_state(business_object, self.user)
response = self.client.get(f'/api/v1/printing-jobs/{job.id}/timeline/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 验证时间线中只有一个已完成的状态(撤销的不显示)
timeline = response.data['results']
completed_states = [t for t in timeline if t['status'] == 'completed']
self.assertEqual(len(completed_states), 1)
self.assertEqual(completed_states[0]['state_name'], self.state1.name)
def test_list_printing_jobs_with_product_image_url(self):
"""测试列表接口返回产品图片 URLmdy_image_url 优先)"""
# 设置产品的 mdy_image_url
self.product.mdy_image_url = 'https://example.com/product.jpg'
self.product.save()
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
response = self.client.get('/api/v1/printing-jobs/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 验证 product_image_url 字段存在且返回正确的 URL
result = response.data['results'][0]
self.assertIn('product_image_url', result)
self.assertEqual(result['product_image_url'], 'https://example.com/product.jpg')
def test_list_printing_jobs_product_image_url_none(self):
"""测试产品无图片时 product_image_url 返回 None"""
# 确保产品没有图片
self.product.mdy_image_url = None
self.product.image = None
self.product.description = None
self.product.save()
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
response = self.client.get('/api/v1/printing-jobs/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 验证 product_image_url 字段存在且为 None
result = response.data['results'][0]
self.assertIn('product_image_url', result)
self.assertIsNone(result['product_image_url'])
def test_list_printing_jobs_product_image_url_fallback_to_local_image(self):
"""测试产品图片 URL fallback 到本地 image 字段"""
from django.core.files.uploadedfile import SimpleUploadedFile
# 确保 mdy_image_url 和 description 都为空
self.product.mdy_image_url = None
self.product.description = None
# 设置本地图片(模拟已上传的文件路径)
self.product.image = 'product_images/test_product.jpg'
self.product.save()
job = printing_models.PrintingJob.objects.create(
printing_order=self.printing_order,
product=self.product,
quantity=100,
unit='',
size='50*60',
pieces=10
)
response = self.client.get('/api/v1/printing-jobs/')
self.assertEqual(response.status_code, status.HTTP_200_OK)
# 验证 product_image_url 字段存在且包含本地图片路径
result = response.data['results'][0]
self.assertIn('product_image_url', result)
self.assertIsNotNone(result['product_image_url'])
self.assertIn('product_images/test_product.jpg', result['product_image_url'])