1
0
forked from erp-dev/erp

feat: stateflow v2 (params required)

This commit is contained in:
2025-11-15 19:39:15 +08:00
parent 9ad404a365
commit 9c38e2ac09
24 changed files with 3670 additions and 547 deletions

View File

@@ -11,56 +11,45 @@ User = get_user_model()
def get_business_object_current_state(business_object: 'models.BusinessObject') -> Optional['models.State']:
"""
获取订单的当前状态
获取业务对象的当前状态(最后完成的状态)
规则:
1. 如果没有任何完成记录(或所有记录都被撤销),返回 None未开始处理
2. 如果有完成的节点,返回第一个未完成的节点(进行中)
3. 如果所有节点都已完成,返回 None流程已完成
"""
# 获取流程的所有节点(按顺序)
process_nodes = business_object.process.process_nodes.select_related('state').order_by('order', 'id')
if not process_nodes.exists():
return None
1. 如果没有任何完成记录(或所有记录都被撤销),返回 None未开始
2. 如果有完成记录,返回最后完成的状态(进行中或已完成
# 获取已完成且未被撤销的状态ID集合
completed_state_ids = set(
business_object.state_logs.filter(is_cancelled=False).values_list('state_id', flat=True)
注意current_state 表示"最后完成的状态",而不是"待执行的状态"
"""
# 获取最后一条完成且未撤销的记录
last_completed_record = (
business_object.state_logs
.filter(is_cancelled=False)
.select_related('state')
.order_by('-completed_at', '-id')
.first()
)
# 如果没有任何有效完成记录,返回 None未开始
if not completed_state_ids:
return None
if last_completed_record:
return last_completed_record.state
# 找到第一个未完成的节点
for node in process_nodes:
if node.state_id not in completed_state_ids:
return node.state
# 所有节点都已完成,返回 None已完成
# 没有任何完成记录,返回 None未开始
return None
def get_business_object_state_status(business_object: 'models.BusinessObject', state: 'models.State') -> str:
"""
获取订单中某个状态的状态
获取业务对象中某个状态的状态
返回值:
- 'not_started': 未开始
- 'in_progress': 进行中
- 'completed': 已完成
注意:由于 current_state 现在表示"最后完成的状态",不再有 'in_progress' 概念
"""
# 检查是否已完成且未被撤销
is_completed = business_object.state_logs.filter(state=state, is_cancelled=False).exists()
if is_completed:
return 'completed'
current_state = get_business_object_current_state(business_object)
# 检查是否为当前状态
if current_state and current_state.id == state.id:
return 'in_progress'
return 'not_started'
@@ -83,50 +72,64 @@ def get_progress_percentage(business_object: 'models.BusinessObject') -> float:
return (completed_count / total_nodes) * 100
def advance_to_next_state(business_object: 'models.BusinessObject', user) -> Tuple[bool, str]:
def advance_to_next_state(business_object: 'models.BusinessObject', user, **parameters) -> Tuple[bool, str, Optional['models.StateFlowRecord']]:
"""
订单推进到下一个状态
业务对象推进到下一个状态(完成下一个未完成的节点)
返回: (是否成功, 消息)
参数:
business_object: BusinessObject 实例
user: 操作用户
**parameters: 状态参数(可选)
返回:
tuple: (success: bool, message: str, state_log: StateFlowRecord|None)
- success: 是否成功
- message: 提示信息
- state_log: 创建的状态日志(成功时)
"""
can_advance, reason = can_advance_to_next_state(business_object)
if not can_advance:
return False, reason
return False, reason, None
with transaction.atomic():
# 获取当前状态(返回第一个未完成的节点
current_state = get_business_object_current_state(business_object)
# 获取下一个待执行的节点
next_pending = get_next_pending_state(business_object, include_parameters=False)
# 如果当前状态为 None说明是初始状态未开始
if current_state is None:
# 获取第一个节点
first_node = business_object.process.process_nodes.order_by('order', 'id').first()
if not first_node:
return False, "流程没有任何节点"
# 标记第一个状态为已完成
models.StateFlowRecord.objects.create(
business_object=business_object,
state=first_node.state,
completed_by=user
)
return True, f"已完成状态: {first_node.state.name}"
if not next_pending:
return False, "没有待执行的节点", None
# current_state 不为 None 时,它一定是未完成的节点
# 标记当前状态为已完成
models.StateFlowRecord.objects.create(
next_state = next_pending['state']
# 验证必填参数
if parameters:
is_valid, missing_params = validate_required_parameters(next_state, **parameters)
if not is_valid:
return False, f"缺失必填参数: {', '.join(missing_params)}", None
else:
# 检查是否有必填参数
required_params = get_required_parameters(next_state)
if required_params.exists():
required_keys = list(required_params.values_list('key', flat=True))
return False, f"缺失必填参数: {', '.join(required_keys)}", None
# 创建状态流转记录
state_log = models.StateFlowRecord.objects.create(
business_object=business_object,
state=current_state,
state=next_state,
completed_by=user
)
# 检查是否所有状态都已完成
next_state = get_business_object_current_state(business_object)
if next_state is None:
# 所有状态都已完成
return True, f"流程已完成,最后状态: {current_state.name}"
# 如果提供了参数,创建参数记录
if parameters:
create_parameter_record(state_log, **parameters)
return True, f"已完成状态: {current_state.name}"
# 检查是否所有状态都已完成
after_advance = get_next_pending_state(business_object, include_parameters=False)
if after_advance is None:
# 所有状态都已完成
return True, f"流程已完成,最后状态: {next_state.name}", state_log
return True, f"已完成状态: {next_state.name}", state_log
def reset_business_object_progress(business_object: 'models.BusinessObject') -> None:
@@ -189,23 +192,24 @@ def get_current_state_parameters(business_object: 'models.BusinessObject') -> Li
def get_overall_status(business_object: 'models.BusinessObject') -> str:
"""
获取订单的整体状态
获取业务对象的整体状态
返回值:
- 'not_started': 未开始(没有任何有效的完成记录)
- 'in_progress': 进行中(有部分状态已完成)
- 'in_progress': 进行中(有部分状态已完成,但未完成所有
- 'completed': 已完成(所有状态都已完成)
"""
current_state = get_business_object_current_state(business_object)
# 检查是否有任何有效的完成记录
has_completed = business_object.state_logs.filter(is_cancelled=False).exists()
if not has_completed:
return 'not_started'
if current_state is None:
# 有完成记录,但当前状态为 None说明所有状态都已完成
# 检查是否有下一个待执行节点
next_pending = get_next_pending_state(business_object, include_parameters=False)
if next_pending is None:
# 没有待执行节点,说明所有状态都已完成
return 'completed'
return 'in_progress'
@@ -213,7 +217,7 @@ def get_overall_status(business_object: 'models.BusinessObject') -> str:
def can_advance_to_next_state(business_object: 'models.BusinessObject') -> Tuple[bool, str]:
"""
检查订单是否可以推进到下一个状态
检查业务对象是否可以推进到下一个状态
返回: (是否可以推进, 原因)
"""
@@ -222,35 +226,27 @@ def can_advance_to_next_state(business_object: 'models.BusinessObject') -> Tuple
if not first_node:
return False, "流程没有任何节点"
current_state = get_business_object_current_state(business_object)
# 获取下一个待执行节点
next_pending = get_next_pending_state(business_object, include_parameters=False)
# 如果当前状态为 None有两种情况
# 1. 没有完成记录 - 未开始
# 2. 所有节点已完成 - 已完成
if current_state is None:
completed_count = business_object.state_logs.filter(is_cancelled=False).count()
if completed_count == 0:
# 未开始,可以推进到第一个状态
return True, f"可以开始处理,将推进到: {first_node.state.name}"
else:
# 所有状态都已完成
return False, "流程已完成,无法继续推进"
if next_pending is None:
# 没有待执行节点,流程已完成
return False, "流程已完成,无法继续推进"
# current_state 不为 None 时,它一定是第一个未完成的节点,可以推进
return True, f"可以推进到: {current_state.name}"
next_state = next_pending['state']
return True, f"可以推进到: {next_state.name}"
def get_business_object_state_timeline(business_object: 'models.BusinessObject') -> List[dict]:
"""
获取订单状态时间线(包括未开始、进行中和已完成的状态)
获取业务对象状态时间线(包括未开始和已完成的状态)
返回格式:
[
{
'state': State对象,
'status': 'not_started' | 'in_progress' | 'completed' | 'cancelled',
'business_object': 顺序号,
'status': 'not_started' | 'completed' | 'cancelled',
'order': 顺序号,
'completed_at': 完成时间(如果已完成),
'completed_by': 完成人(如果已完成),
'cancelled_at': 撤销时间(如果已撤销),
@@ -261,7 +257,6 @@ def get_business_object_state_timeline(business_object: 'models.BusinessObject')
"""
timeline = []
process_nodes = business_object.process.process_nodes.select_related('state').order_by('order', 'id')
current_state = get_business_object_current_state(business_object)
# 构建状态日志映射(包括已撤销的记录)
state_logs_map = {
@@ -282,12 +277,6 @@ def get_business_object_state_timeline(business_object: 'models.BusinessObject')
completed_by = log.completed_by
cancelled_at = log.cancelled_at
is_cancelled = log.is_cancelled
elif current_state and state.id == current_state.id:
status = 'in_progress'
completed_at = None
completed_by = None
cancelled_at = None
is_cancelled = False
else:
status = 'not_started'
completed_at = None
@@ -306,3 +295,169 @@ def get_business_object_state_timeline(business_object: 'models.BusinessObject')
})
return timeline
def get_next_pending_state(
business_object: 'models.BusinessObject',
include_parameters: bool = True
) -> Optional[dict]:
"""
获取指定业务对象的下一个待执行节点(第一个未完成的节点)
Args:
business_object: 业务对象
include_parameters: 是否包含参数列表,默认 True
Returns:
包含状态信息的字典,如果没有待执行节点则返回 None
{
'state': State对象,
'order': 节点顺序号,
'parameters': [StateParameter列表] (如果 include_parameters=True)
}
"""
# 获取所有节点(按顺序)
process_nodes = business_object.process.process_nodes.select_related('state').order_by('order', 'id')
if not process_nodes.exists():
return None
# 获取已完成且未被撤销的状态ID集合
completed_state_ids = set(
business_object.state_logs.filter(is_cancelled=False).values_list('state_id', flat=True)
)
# 找到第一个未完成的节点
for node in process_nodes:
if node.state_id not in completed_state_ids:
result = {
'state': node.state,
'order': node.order,
}
if include_parameters:
result['parameters'] = list(node.state.parameters.all())
return result
# 所有节点都已完成
return None
def get_all_pending_states(business_object: 'models.BusinessObject') -> List[dict]:
"""
获取指定业务对象所有待执行的节点列表(不包含参数)
Args:
business_object: 业务对象
Returns:
待执行节点列表,每个元素包含:
{
'state': State对象,
'order': 节点顺序号
}
"""
# 获取所有节点
process_nodes = business_object.process.process_nodes.select_related('state').order_by('order', 'id')
# 获取已完成且未被撤销的状态ID集合
completed_state_ids = set(
business_object.state_logs.filter(is_cancelled=False).values_list('state_id', flat=True)
)
# 找到所有未完成的节点
pending_nodes = []
for node in process_nodes:
if node.state_id not in completed_state_ids:
pending_nodes.append({
'state': node.state,
'order': node.order,
})
return pending_nodes
def get_state_parameters(
state: 'models.State',
required_only: bool = False
) -> List['models.StateParameter']:
"""
获取指定节点的所有参数列表
Args:
state: 状态节点
required_only: 是否仅包含必填参数,默认 False
Returns:
参数列表
"""
return state.get_parameters(required_only=required_only)
def get_required_parameters(state: 'models.State'):
"""
获取状态的所有必填参数
参数:
state: State 实例
返回:
QuerySet: 必填的 StateParameter 对象
"""
return state.parameters.filter(is_required=True)
def validate_required_parameters(state: 'models.State', **kwargs) -> Tuple[bool, List[str]]:
"""
验证必填参数是否全部提供
参数:
state: State 实例
**kwargs: 用户提供的参数字典
返回:
tuple: (is_valid: bool, missing_params: list)
- is_valid: 是否通过验证
- missing_params: 缺失的必填参数 key 列表
"""
required_params = get_required_parameters(state)
required_keys = list(required_params.values_list('key', flat=True))
provided_keys = set(kwargs.keys())
missing_keys = [key for key in required_keys if key not in provided_keys]
is_valid = len(missing_keys) == 0
return is_valid, missing_keys
def create_parameter_record(state_log: 'models.StateFlowRecord', remark: str = '', **parameters) -> 'models.StateLogParameterRecord':
"""
为状态流转记录创建参数记录
参数:
state_log: StateFlowRecord 实例
remark: 备注
**parameters: 参数字典(直接保存,不做验证)
返回:
StateLogParameterRecord: 创建的参数记录
"""
record = models.StateLogParameterRecord.objects.create(
state_log=state_log,
parameters=parameters, # 直接保存为 JSON
remark=remark
)
return record
def add_parameters_to_state_log(state_log: 'models.StateFlowRecord', remark: str = '', **parameters) -> 'models.StateLogParameterRecord':
"""
为已有的状态流转记录补充参数支持重复key
参数:
state_log: StateFlowRecord 实例
remark: 备注
**parameters: 参数字典
返回:
StateLogParameterRecord: 创建的参数记录
"""
return create_parameter_record(state_log, remark, **parameters)