1
0
forked from erp-dev/erp
Files
erpnew/stateflow/services.py
2025-11-12 17:08:20 +08:00

278 lines
9.4 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.
"""
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) -> Tuple[bool, str]:
"""
将订单推进到下一个状态
返回: (是否成功, 消息)
"""
can_advance, reason = can_advance_to_next_state(business_object)
if not can_advance:
return False, reason
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}"
# current_state 不为 None 时,它一定是未完成的节点
# 标记当前状态为已完成
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_business_object_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()
)
# 向后兼容的别名
reset_order_progress = reset_business_object_progress
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有两种情况
# 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, "流程已完成,无法继续推进"
# current_state 不为 None 时,它一定是第一个未完成的节点,可以推进
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