1
0
forked from erp-dev/erp

patch: patch testing (api & services)

This commit is contained in:
2025-11-17 10:10:32 +08:00
parent 9c38e2ac09
commit 02a1b77e19
10 changed files with 337 additions and 102 deletions

View File

@@ -11,27 +11,24 @@ User = get_user_model()
def get_business_object_current_state(business_object: 'models.BusinessObject') -> Optional['models.State']:
"""
获取业务对象的当前状态(最后完成的状态
获取业务对象的当前状态(下一个待执行的节点
规则:
1. 如果没有任何完成记录(或所有记录都被撤销),返回 None未开始
2. 如果有完成记录,返回最后完成的状态(进行中或已完成)
- 返回下一个待执行的节点(第一个未完成的节点
- 如果所有节点都已完成,返回 None
注意current_state 表示"最后完成的状态",而不是"待执行的状态"
注意current_state 始终返回"下一个待执行的节点",配合进度可以判断具体状态
- 进度 0% + current_state 非空 → 显示"未开始"(还没开始执行,但知道第一个节点是什么)
- 进度 > 0% + current_state 非空 → 显示 current_state.name正在进行
- current_state = None → 显示"已完成"(没有待执行的节点了)
"""
# 获取最后一条完成且未撤销的记录
last_completed_record = (
business_object.state_logs
.filter(is_cancelled=False)
.select_related('state')
.order_by('-completed_at', '-id')
.first()
)
# 使用 get_next_pending_state 获取下一个待执行的节点
next_pending = get_next_pending_state(business_object, include_parameters=False)
if last_completed_record:
return last_completed_record.state
if next_pending:
return next_pending['state']
# 没有任何完成记录,返回 None(未开始)
# 所有节点都已完成,返回 None
return None
@@ -239,13 +236,13 @@ def can_advance_to_next_state(business_object: 'models.BusinessObject') -> Tuple
def get_business_object_state_timeline(business_object: 'models.BusinessObject') -> List[dict]:
"""
获取业务对象状态时间线(包括未开始和已完成的状态)
获取业务对象状态时间线(包括未开始、进行中和已完成的状态)
返回格式:
[
{
'state': State对象,
'status': 'not_started' | 'completed' | 'cancelled',
'status': 'not_started' | 'in_progress' | 'completed' | 'cancelled',
'order': 顺序号,
'completed_at': 完成时间(如果已完成),
'completed_by': 完成人(如果已完成),
@@ -254,6 +251,12 @@ def get_business_object_state_timeline(business_object: 'models.BusinessObject')
},
...
]
状态判断规则:
- cancelled: 有日志记录且已撤销
- completed: 有日志记录且未撤销
- in_progress: 没有日志记录,但是 current_state下一个待执行的节点且已有完成记录
- not_started: 其他未开始的节点
"""
timeline = []
process_nodes = business_object.process.process_nodes.select_related('state').order_by('order', 'id')
@@ -264,6 +267,13 @@ def get_business_object_state_timeline(business_object: 'models.BusinessObject')
for log in business_object.state_logs.select_related('completed_by')
}
# 获取 current_state下一个待执行的节点
current_state = get_business_object_current_state(business_object)
current_state_id = current_state.id if current_state else None
# 检查是否有任何完成记录
has_any_completed = business_object.state_logs.filter(is_cancelled=False).exists()
for node in process_nodes:
state = node.state
log = state_logs_map.get(state.id)
@@ -278,7 +288,12 @@ def get_business_object_state_timeline(business_object: 'models.BusinessObject')
cancelled_at = log.cancelled_at
is_cancelled = log.is_cancelled
else:
status = 'not_started'
# 未开始的节点,判断是 in_progress 还是 not_started
# 如果是 current_state 且有任何完成记录,则为 in_progress
if has_any_completed and state.id == current_state_id:
status = 'in_progress'
else:
status = 'not_started'
completed_at = None
completed_by = None
cancelled_at = None

View File

@@ -565,48 +565,30 @@ class BusinessObjectNewAPITestCase(TestCase):
self.assertEqual(response.data['count'], 0)
def test_current_state_parameters_api(self):
"""测试获取当前状态(最后完成状态)参数 API"""
# 未开始时current_state 为 None应返回无参数消息
"""测试获取当前状态(下一个待执行状态)参数 API"""
# 未开始时current_state 为 state1第一个待执行的应返回 state1 的参数
response = self.client.get(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/current_state_parameters/'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data['parameters']), 0)
self.assertIn('message', response.data)
self.assertEqual(response.data['state']['id'], self.state1.id)
self.assertEqual(len(response.data['parameters']), 2)
# 推进完成第一个状态current_state 变为 state1最后完成的状态)
# 推进完成第一个状态current_state 变为 state2下一个待执行的状态)
self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'param1': 'test_value'}},
format='json'
)
# 现在 current_state 是 state1参数)
response = self.client.get(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/current_state_parameters/'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['state']['id'], self.state1.id)
self.assertEqual(response.data['count'], 2)
self.assertEqual(len(response.data['parameters']), 2)
# 测试只获取必填参数
response = self.client.get(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/current_state_parameters/?required_only=true'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['count'], 1)
self.assertEqual(response.data['parameters'][0]['key'], 'param1')
# 推进完成第二个状态current_state 变为 state2无参数
self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/')
# 现在 current_state 是 state2参数)
response = self.client.get(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/current_state_parameters/'
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data['state']['id'], self.state2.id)
self.assertEqual(response.data['count'], 0)
self.assertEqual(len(response.data['parameters']), 0)
def test_state_parameters_api(self):
"""测试 State 的参数 API"""

View File

@@ -73,9 +73,10 @@ class BusinessObjectAPITestCase(TestCase):
self.assertTrue(response.data['success'])
self.assertIn('重置', response.data['message'])
# 验证进度已重置
# 验证进度已重置current_state 返回第一个待执行的节点)
self.business_object.refresh_from_db()
self.assertIsNone(services.get_business_object_current_state(self.business_object))
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.name, self.state1.name)
self.assertEqual(self.business_object.state_logs.filter(is_cancelled=False).count(), 0)
def test_reset_api_not_started(self):
@@ -120,8 +121,8 @@ class BusinessObjectAPITestCase(TestCase):
self.assertEqual(response.data[0]['completed_by'], 'testuser')
self.assertFalse(response.data[0]['is_cancelled'])
# 其他状态仍是待执行
self.assertEqual(response.data[1]['status'], 'not_started')
# 第二个状态是进行中(下一个待执行的)
self.assertEqual(response.data[1]['status'], 'in_progress')
self.assertEqual(response.data[2]['status'], 'not_started')
def test_timeline_api_with_step_back(self):
@@ -469,3 +470,59 @@ class BusinessObjectAPITestCase(TestCase):
self.assertIsNotNone(item['completed_at'])
self.assertIsNotNone(item['completed_by'])
self.assertFalse(item['is_cancelled'])
def test_advance_when_already_completed(self):
"""测试流程已完成时尝试推进(预期错误)"""
# 完成所有状态
self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/',
{'parameters': {'temperature': '25.5'}},
format='json'
)
self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/')
self.client.post(f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/')
# 验证已完成
self.assertEqual(services.get_overall_status(self.business_object), 'completed')
# 尝试再次推进(应该失败)
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/advance/'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertFalse(response.data['success'])
self.assertIn('流程已完成', response.data['message'])
self.assertIn('无法继续推进', response.data['message'])
def test_step_back_when_not_started(self):
"""测试未开始时尝试回退(预期错误)"""
# 验证未开始
self.assertEqual(services.get_overall_status(self.business_object), 'not_started')
# 尝试回退(应该失败)
response = self.client.post(
f'/api/v1/stateflow/business-objects/{self.business_object.id}/step_back/'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertFalse(response.data['success'])
self.assertIn('没有任何状态流转记录', response.data['message'])
def test_advance_process_without_nodes(self):
"""测试流程没有节点时尝试推进(预期错误)"""
# 创建一个没有节点的流程
empty_process = models.Process.objects.create(name='空流程')
empty_business_object = models.BusinessObject.objects.create(
name='空业务对象',
process=empty_process
)
# 尝试推进(应该失败)
response = self.client.post(
f'/api/v1/stateflow/business-objects/{empty_business_object.id}/advance/'
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertFalse(response.data['success'])
self.assertIn('流程没有任何节点', response.data['message'])

View File

@@ -72,9 +72,9 @@ class ParameterManagementTestCase(TestCase):
self.assertIn('temperature', message)
self.assertIsNone(state_log)
# 验证状态未改变
# 验证状态未改变(仍然是第一个待执行的节点)
current_state = services.get_business_object_current_state(self.business_object)
self.assertIsNone(current_state)
self.assertEqual(current_state.name, self.state1.name)
# 验证没有创建状态流转记录
self.assertEqual(self.business_object.state_logs.count(), 0)
@@ -92,9 +92,9 @@ class ParameterManagementTestCase(TestCase):
self.assertTrue(success)
self.assertIsNotNone(state_log)
# 验证状态已推进
# 验证状态已推进(下一个待执行的是 state2
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.name, self.state1.name)
self.assertEqual(current_state.name, self.state2.name)
# 验证参数记录已创建
self.assertEqual(state_log.parameter_records.count(), 1)

View File

@@ -42,9 +42,9 @@ class StateFlowServicesTestCase(TestCase):
)
def test_initial_state(self):
"""测试初始状态 - 应该是 None未开始"""
"""测试初始状态 - 应该返回第一个待执行的节点"""
current_state = self.business_object.get_current_state()
self.assertIsNone(current_state, '初始状态应该是 None未开始')
self.assertEqual(current_state.name, self.state1.name, '初始状态应该返回第一个待执行的节点')
self.assertEqual(self.business_object.get_progress_percentage(), 0.0)
# 整体状态应该是 not_started
@@ -57,18 +57,18 @@ class StateFlowServicesTestCase(TestCase):
success, message, _ = services.advance_to_next_state(self.business_object, self.user)
self.assertTrue(success)
# current_state 应该是最后完成的状态state1
# current_state 应该是下一个待执行的状态state2
current_state = self.business_object.get_current_state()
self.assertEqual(current_state.name, self.state1.name)
self.assertEqual(current_state.name, self.state2.name)
self.assertAlmostEqual(self.business_object.get_progress_percentage(), 33.33, places=1)
# 推进到状态2完成state2
success, message, _ = services.advance_to_next_state(self.business_object, self.user)
self.assertTrue(success)
# current_state 应该是 state2
# current_state 应该是 state3下一个待执行的
current_state = self.business_object.get_current_state()
self.assertEqual(current_state.name, self.state2.name)
self.assertEqual(current_state.name, self.state3.name)
self.assertAlmostEqual(self.business_object.get_progress_percentage(), 66.67, places=1)
# 完成最后一个状态state3
@@ -76,9 +76,9 @@ class StateFlowServicesTestCase(TestCase):
self.assertTrue(success)
self.assertEqual(self.business_object.get_progress_percentage(), 100.0)
# current_state 应该是 state3最后完成的状态
# current_state 应该是 None没有待执行的节点了
current_state = self.business_object.get_current_state()
self.assertEqual(current_state.name, self.state3.name)
self.assertIsNone(current_state)
# 整体状态应该是 completed
status = services.get_overall_status(self.business_object)
@@ -116,7 +116,7 @@ class StateFlowServicesTestCase(TestCase):
timeline = self.business_object.get_timeline()
self.assertEqual(timeline[0]['status'], 'completed')
self.assertEqual(timeline[1]['status'], 'not_started')
self.assertEqual(timeline[1]['status'], 'in_progress') # 下一个待执行的节点
self.assertEqual(timeline[2]['status'], 'not_started')
self.assertIsNotNone(timeline[0]['completed_by'])
self.assertEqual(timeline[0]['completed_by'].id, self.user.id)
@@ -141,9 +141,9 @@ class StateFlowServicesTestCase(TestCase):
cancelled_count = self.business_object.state_logs.filter(is_cancelled=True).count()
self.assertEqual(cancelled_count, 2)
# 应该回到初始状态(未开始
# 应该回到初始状态(返回第一个待执行的节点
current_state = self.business_object.get_current_state()
self.assertIsNone(current_state)
self.assertEqual(current_state.name, self.state1.name)
# 所有撤销的记录都应该有撤销时间
for log in self.business_object.state_logs.all():

View File

@@ -51,9 +51,9 @@ class StepBackTestCase(TestCase):
success, _, _ = services.advance_to_next_state(self.business_object, self.user)
self.assertTrue(success)
# 验证 current_state 是 state1最后完成的状态)
# 验证 current_state 是 state2下一个待执行的状态)
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.name, self.state1.name)
self.assertEqual(current_state.name, self.state2.name)
# 回退一步
success, message = services.step_back_one_state(self.business_object, self.user)
@@ -63,7 +63,7 @@ class StepBackTestCase(TestCase):
# 验证回到未开始状态
self.assertEqual(services.get_overall_status(self.business_object), 'not_started')
current_state = services.get_business_object_current_state(self.business_object)
self.assertIsNone(current_state)
self.assertEqual(current_state.name, self.state1.name)
# 验证状态1的记录已被撤销
record = models.StateFlowRecord.objects.filter(
@@ -79,18 +79,18 @@ class StepBackTestCase(TestCase):
services.advance_to_next_state(self.business_object, self.user)
services.advance_to_next_state(self.business_object, self.user)
# 验证 current_state 是 state2最后完成的状态)
# 验证 current_state 是 state3下一个待执行的状态)
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.name, self.state2.name)
self.assertEqual(current_state.name, self.state3.name)
# 回退一步
success, message = services.step_back_one_state(self.business_object, self.user)
self.assertTrue(success)
self.assertIn('状态2', message)
# 验证 current_state 变为state1
# 验证 current_state 变为 state2下一个待执行的
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.name, self.state1.name)
self.assertEqual(current_state.name, self.state2.name)
# 验证状态2的记录已被撤销
record = models.StateFlowRecord.objects.filter(
@@ -106,20 +106,20 @@ class StepBackTestCase(TestCase):
services.advance_to_next_state(self.business_object, self.user)
services.advance_to_next_state(self.business_object, self.user)
# 验证已完成current_state 是 state3
# 验证已完成current_state 是 None没有待执行的节点
self.assertEqual(services.get_overall_status(self.business_object), 'completed')
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.name, self.state3.name)
self.assertIsNone(current_state)
# 回退一步
success, message = services.step_back_one_state(self.business_object, self.user)
self.assertTrue(success)
self.assertIn('状态3', message)
# 验证回到进行中状态current_state 变为state2
# 验证回到进行中状态current_state 变为 state3下一个待执行的
self.assertEqual(services.get_overall_status(self.business_object), 'in_progress')
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.name, self.state2.name)
self.assertEqual(current_state.name, self.state3.name)
def test_step_back_multiple_times(self):
"""测试多次回退"""
@@ -132,13 +132,13 @@ class StepBackTestCase(TestCase):
success, _ = services.step_back_one_state(self.business_object, self.user)
self.assertTrue(success)
current = services.get_business_object_current_state(self.business_object)
self.assertEqual(current.name, self.state2.name)
self.assertEqual(current.name, self.state3.name)
# 第二次回退(撤销 state2
success, _ = services.step_back_one_state(self.business_object, self.user)
self.assertTrue(success)
current = services.get_business_object_current_state(self.business_object)
self.assertEqual(current.name, self.state1.name)
self.assertEqual(current.name, self.state2.name) # 下一个待执行的是 state2
# 第三次回退
success, _ = services.step_back_one_state(self.business_object, self.user)
@@ -163,9 +163,9 @@ class StepBackTestCase(TestCase):
success, _, _ = services.advance_to_next_state(self.business_object, self.user)
self.assertTrue(success)
# 验证 current_state 是 state2最后完成的)
# 验证 current_state 是 state3下一个待执行的)
current_state = services.get_business_object_current_state(self.business_object)
self.assertEqual(current_state.name, self.state2.name)
self.assertEqual(current_state.name, self.state3.name)
def test_step_back_preserves_history(self):
"""测试回退操作保留历史记录"""