1
0
forked from erp-dev/erp

feat: new modul (stateflow)

This commit is contained in:
2025-11-11 18:05:10 +08:00
parent 2aafb93aad
commit 9898d71a7e
30 changed files with 3723 additions and 7 deletions

275
stateflow/services.py Normal file
View File

@@ -0,0 +1,275 @@
"""
Stateflow业务逻辑服务层
"""
from typing import List, Optional, Tuple
from django.contrib.auth import get_user_model
from django.db import transaction
from . import models
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
# 获取已完成且未被撤销的状态ID集合
completed_state_ids = set(
business_object.state_logs.filter(is_cancelled=False).values_list('state_id', flat=True)
)
# 如果没有任何有效完成记录,返回 None未开始
if not completed_state_ids:
return None
# 找到第一个未完成的节点
for node in process_nodes:
if node.state_id not in completed_state_ids:
return node.state
# 所有节点都已完成,返回 None已完成
return None
def get_business_object_state_status(business_object: 'models.BusinessObject', state: 'models.State') -> str:
"""
获取订单中某个状态的状态
返回值:
- 'not_started': 未开始
- 'in_progress': 进行中
- 'completed': 已完成
"""
# 检查是否已完成且未被撤销
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'
def get_completed_node_ids(business_object: 'models.BusinessObject') -> List[int]:
"""获取订单已完成且未撤销的节点ID列表按完成顺序"""
return list(
business_object.state_logs.filter(is_cancelled=False)
.order_by('completed_at')
.values_list('state_id', flat=True)
)
def get_progress_percentage(business_object: 'models.BusinessObject') -> float:
"""计算订单进度百分比"""
total_nodes = business_object.process.process_nodes.count()
if total_nodes == 0:
return 0.0
completed_count = business_object.state_logs.filter(is_cancelled=False).count()
return (completed_count / total_nodes) * 100
def advance_to_next_state(business_object: 'models.BusinessObject', user: 'User') -> Tuple[bool, str]:
"""
将订单推进到下一个状态
返回: (是否成功, 消息)
"""
with transaction.atomic():
# 获取当前状态
current_state = get_business_object_current_state(business_object)
# 如果当前状态为 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 business_object.state_logs.filter(state=current_state, is_cancelled=False).exists():
return False, f"当前状态 '{current_state.name}' 已完成"
# 标记当前状态为已完成
models.StateFlowRecord.objects.create(
business_object=business_object,
state=current_state,
completed_by=user
)
# 检查是否所有状态都已完成
next_state = get_business_object_current_state(business_object)
if next_state is None:
# 所有状态都已完成
return True, f"流程已完成,最后状态: {current_state.name}"
return True, f"已完成状态: {current_state.name}"
def reset_order_progress(business_object: 'models.BusinessObject') -> None:
"""
重置订单进度
注意:不删除历史记录,而是标记所有记录为已撤销,并记录撤销时间
这样可以保留完整的操作历史
"""
from django.utils import timezone
business_object.state_logs.filter(is_cancelled=False).update(
is_cancelled=True,
cancelled_at=timezone.now()
)
def get_current_state_parameters(business_object: 'models.BusinessObject') -> List['models.StateParameter']:
"""获取订单当前状态的参数列表"""
current_state = get_business_object_current_state(business_object)
if current_state:
return list(current_state.parameters.all())
return []
def get_overall_status(business_object: 'models.BusinessObject') -> str:
"""
获取订单的整体状态
返回值:
- 'not_started': 未开始(没有任何有效的完成记录)
- '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说明所有状态都已完成
return 'completed'
return 'in_progress'
def can_advance_to_next_state(business_object: 'models.BusinessObject') -> Tuple[bool, str]:
"""
检查订单是否可以推进到下一个状态
返回: (是否可以推进, 原因)
"""
# 检查流程是否有节点
first_node = business_object.process.process_nodes.order_by('order', 'id').first()
if not first_node:
return False, "流程没有任何节点"
current_state = get_business_object_current_state(business_object)
# 如果当前状态为 None
if current_state is None:
# 检查是否所有状态都已完成
total_nodes = business_object.process.process_nodes.count()
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 business_object.state_logs.filter(state=current_state, is_cancelled=False).exists():
return False, f"当前状态 '{current_state.name}' 已完成,无法重复完成"
return True, f"可以推进到: {current_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': 顺序号,
'completed_at': 完成时间(如果已完成),
'completed_by': 完成人(如果已完成),
'cancelled_at': 撤销时间(如果已撤销),
'is_cancelled': 是否已撤销,
},
...
]
"""
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 = {
log.state_id: log
for log in business_object.state_logs.select_related('completed_by')
}
for node in process_nodes:
state = node.state
log = state_logs_map.get(state.id)
if log:
if log.is_cancelled:
status = 'cancelled'
else:
status = 'completed'
completed_at = log.completed_at
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
completed_by = None
cancelled_at = None
is_cancelled = False
timeline.append({
'state': state,
'status': status,
'order': node.order,
'completed_at': completed_at,
'completed_by': completed_by,
'cancelled_at': cancelled_at,
'is_cancelled': is_cancelled,
})
return timeline