forked from erp-dev/erp
573 lines
19 KiB
Python
573 lines
19 KiB
Python
"""
|
||
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_last_completed_state(business_object: 'models.BusinessObject') -> Optional['models.State']:
|
||
"""
|
||
获取最后完成的状态
|
||
|
||
返回:最后一个已完成的状态节点,如果没有则返回 None
|
||
|
||
应用场景:
|
||
- 显示"刚完成了什么"
|
||
- 查询最后完成状态的参数
|
||
- 审计日志
|
||
"""
|
||
last_log = business_object.state_logs.filter(
|
||
is_cancelled=False
|
||
).order_by('-completed_at', '-id').first()
|
||
|
||
return last_log.state if last_log else None
|
||
|
||
|
||
def get_next_pending_state_simple(business_object: 'models.BusinessObject') -> Optional['models.State']:
|
||
"""
|
||
获取下一个待执行的状态(简化版,只返回State对象)
|
||
|
||
返回:下一个待执行的状态节点,如果没有则返回 None
|
||
|
||
应用场景:
|
||
- 显示"接下来要做什么"
|
||
- 获取下一步需要的参数
|
||
- 流程推进前的检查
|
||
"""
|
||
next_pending = get_next_pending_state(business_object, include_parameters=False)
|
||
return next_pending['state'] if next_pending else None
|
||
|
||
|
||
def get_business_object_current_state(business_object: 'models.BusinessObject') -> Optional['models.State']:
|
||
"""
|
||
获取业务对象的当前状态(可配置模式)
|
||
|
||
根据 settings.STATEFLOW_CURRENT_STATE_MODE 决定返回:
|
||
- 'NEXT' (默认): 下一个待执行的节点
|
||
- 'LAST': 最后完成的节点
|
||
|
||
注意:此函数保持原有接口不变,但行为可配置
|
||
推荐:在新代码中直接使用 get_last_completed_state() 或 get_next_pending_state_simple() 以明确语义
|
||
|
||
NEXT模式的规则:
|
||
- 返回下一个待执行的节点(第一个未完成的节点)
|
||
- 如果所有节点都已完成,返回 None
|
||
- 配合进度可以判断具体状态:
|
||
- 进度 0% + current_state 非空 → 显示"未开始"
|
||
- 进度 > 0% + current_state 非空 → 显示 current_state.name(正在进行)
|
||
- current_state = None → 显示"已完成"
|
||
|
||
LAST模式的规则:
|
||
- 返回最后一个已完成的节点
|
||
- 如果没有完成任何节点,返回 None
|
||
"""
|
||
from django.conf import settings
|
||
mode = getattr(settings, 'STATEFLOW_CURRENT_STATE_MODE', 'NEXT')
|
||
|
||
if mode == 'LAST':
|
||
return get_last_completed_state(business_object)
|
||
else: # 默认 'NEXT'
|
||
return get_next_pending_state_simple(business_object)
|
||
|
||
|
||
def get_business_object_state_status(business_object: 'models.BusinessObject', state: 'models.State') -> str:
|
||
"""
|
||
获取业务对象中某个状态的状态
|
||
|
||
返回值:
|
||
- 'not_started': 未开始
|
||
- 'completed': 已完成
|
||
|
||
注意:由于 current_state 现在表示"最后完成的状态",不再有 'in_progress' 概念
|
||
"""
|
||
# 检查是否已完成且未被撤销
|
||
is_completed = business_object.state_logs.filter(state=state, is_cancelled=False).exists()
|
||
if is_completed:
|
||
return 'completed'
|
||
|
||
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, **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, None
|
||
|
||
with transaction.atomic():
|
||
# 获取下一个待执行的节点
|
||
next_pending = get_next_pending_state(business_object, include_parameters=False)
|
||
|
||
if not next_pending:
|
||
return False, "没有待执行的节点", None
|
||
|
||
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=next_state,
|
||
completed_by=user
|
||
)
|
||
|
||
# 如果提供了参数,创建参数记录
|
||
if parameters:
|
||
create_parameter_record(state_log, **parameters)
|
||
|
||
# 检查是否所有状态都已完成
|
||
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:
|
||
"""
|
||
重置业务对象进度
|
||
|
||
注意:不删除历史记录,而是标记所有记录为已撤销,并记录撤销时间
|
||
这样可以保留完整的操作历史,支持审计需求
|
||
"""
|
||
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 step_back_one_state(business_object: 'models.BusinessObject', user) -> Tuple[bool, str]:
|
||
"""
|
||
回退一步(撤销最后一次完成的状态)
|
||
|
||
这是 advance_to_next_state 的逆向操作
|
||
|
||
返回: (是否成功, 消息)
|
||
"""
|
||
from django.utils import timezone
|
||
|
||
with transaction.atomic():
|
||
# 获取最后一条有效的状态流转记录(按完成时间倒序)
|
||
last_record = (
|
||
business_object.state_logs
|
||
.filter(is_cancelled=False)
|
||
.order_by('-completed_at', '-id')
|
||
.first()
|
||
)
|
||
|
||
# 如果没有任何有效记录,说明当前没有任何状态流转,无需回退
|
||
if not last_record:
|
||
return False, "当前没有任何状态流转记录,无法回退"
|
||
|
||
# 标记最后一条记录为已撤销
|
||
last_record.is_cancelled = True
|
||
last_record.cancelled_at = timezone.now()
|
||
last_record.save(update_fields=['is_cancelled', 'cancelled_at'])
|
||
|
||
return True, f"已回退状态: {last_record.state.name}"
|
||
|
||
|
||
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_display_status(business_object: 'models.BusinessObject') -> str:
|
||
"""
|
||
获取用于显示的状态文本
|
||
|
||
规则:
|
||
- 如果没有 business_object:返回第一个节点名称(如果有流程),否则返回空字符串
|
||
- 如果 current_state 为 None:返回"已完成"
|
||
- 否则:返回 current_state 的名称
|
||
|
||
注意:此函数废除了"未开始"的概念,直接显示节点名称
|
||
|
||
返回值: 状态显示文本(字符串)
|
||
"""
|
||
if not business_object:
|
||
return ''
|
||
|
||
current_state = get_business_object_current_state(business_object)
|
||
|
||
if current_state is None:
|
||
# 没有待执行的节点,已完成
|
||
return '已完成'
|
||
|
||
# 直接返回当前状态名称,不管进度如何
|
||
return current_state.name
|
||
|
||
|
||
def get_overall_status(business_object: 'models.BusinessObject') -> str:
|
||
"""
|
||
获取业务对象的整体状态
|
||
|
||
返回值:
|
||
- 'in_progress': 进行中(有部分状态已完成,但未完成所有)
|
||
- 'completed': 已完成(所有状态都已完成)
|
||
|
||
注意:废除了 'not_started' 状态,进度为0时也返回 'in_progress'
|
||
"""
|
||
# 检查是否有下一个待执行节点
|
||
next_pending = get_next_pending_state(business_object, include_parameters=False)
|
||
|
||
if next_pending is 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, "流程没有任何节点"
|
||
|
||
# 获取下一个待执行节点
|
||
next_pending = get_next_pending_state(business_object, include_parameters=False)
|
||
|
||
if next_pending is None:
|
||
# 没有待执行节点,流程已完成
|
||
return False, "流程已完成,无法继续推进"
|
||
|
||
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' | 'completed' | 'cancelled',
|
||
'order': 顺序号,
|
||
'completed_at': 完成时间(如果已完成),
|
||
'completed_by': 完成人(如果已完成),
|
||
'cancelled_at': 撤销时间(如果已撤销),
|
||
'is_cancelled': 是否已撤销,
|
||
},
|
||
...
|
||
]
|
||
|
||
状态判断规则:
|
||
- cancelled: 有日志记录且已撤销
|
||
- completed: 有日志记录且未撤销
|
||
- not_started: 没有日志记录(还未完成)
|
||
|
||
注意:移除了 'in_progress' 状态,因为在当前设计中:
|
||
- current_state 表示"下一个待执行的节点",它还未开始执行,所以是 not_started
|
||
- 一个节点要么已完成,要么还未开始,没有"进行中"的中间状态
|
||
"""
|
||
timeline = []
|
||
process_nodes = business_object.process.process_nodes.select_related('state').order_by('order', 'id')
|
||
|
||
# 构建状态日志映射(包括已撤销的记录)
|
||
state_logs_map = {
|
||
log.state_id: log
|
||
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)
|
||
|
||
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
|
||
else:
|
||
# 未开始的节点
|
||
# current_state 是"下一个待执行的节点",它还未开始,所以状态是 not_started
|
||
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
|
||
|
||
|
||
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)
|
||
|
||
|
||
def get_process_nodes(business_object: 'models.BusinessObject') -> List[dict]:
|
||
"""
|
||
获取业务对象所属流程的所有状态节点列表
|
||
|
||
参数:
|
||
business_object: BusinessObject 实例
|
||
|
||
返回:
|
||
节点列表,每个元素包含:
|
||
{
|
||
'id': ProcessNode ID,
|
||
'state': State对象,
|
||
'state_id': State ID,
|
||
'state_name': State 名称,
|
||
'order': 节点顺序号
|
||
}
|
||
"""
|
||
process_nodes = business_object.process.process_nodes.select_related('state').order_by('order', 'id')
|
||
|
||
return [
|
||
{
|
||
'id': node.id,
|
||
'state': node.state,
|
||
'state_id': node.state_id,
|
||
'state_name': node.state.name,
|
||
'order': node.order,
|
||
}
|
||
for node in process_nodes
|
||
]
|