""" Stateflow业务逻辑服务层 """ import copy 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 ] def clone_business_object( source: 'models.BusinessObject', *, new_object_id: int, expected_content_type_id: int, ) -> 'models.BusinessObject': """ 克隆一个 BusinessObject(含所有状态流转记录与工艺参数记录) 目标: - 不影响原对象 - 克隆出“业务数据完全一致”的新对象(process/描述/关联对象/日志/参数等) - 返回新的 BusinessObject 实例 说明: - 会克隆 source.state_logs 的所有记录(含已撤销记录) - 会克隆每条 StateFlowRecord 下的所有 StateLogParameterRecord(含参数 JSON 与 remark) - 会尽量保持 completed_at / cancelled_at / 参数记录 created_at 与原对象一致 - created_at/updated_at 等 ModelBase 字段也会被同步(使用 update 绕过 auto_now*) - **重要**:克隆必须提供新的 object_id(当 source.content_type 非空时),否则克隆与业务绑定无差异,容易造成误用 - expected_content_type_id 仅用于校验调用方意图:必须与 source.content_type_id 一致,否则拒绝克隆 """ if source is None: raise ValueError("source 不能为空") # 重新加载 source,确保拿到完整关系(避免调用方未预取导致 N+1) source = ( models.BusinessObject.objects .select_related('process', 'content_type') .prefetch_related( 'state_logs__state', 'state_logs__completed_by', 'state_logs__parameter_records', ) .get(id=source.id) ) # 校验 content_type 一致性(不做额外校验,仅比对 id) if source.content_type_id != expected_content_type_id: raise ValueError("content_type 与源对象不一致,拒绝克隆") # 如果源对象未绑定 content_type,则拒绝克隆 if source.content_type_id is None: return ValueError("源对象未绑定 content_type,拒绝克隆") else: if new_object_id is None: raise ValueError("必须提供新的 object_id") if source.object_id == new_object_id: raise ValueError("object_id 必须与源对象不同") content_type = models.ContentType.objects.get(id=source.content_type_id) actual_object = content_type.model_class().objects.get(id=new_object_id) if actual_object is None: raise ValueError("实际对象不存在,拒绝克隆") with transaction.atomic(): cloned = models.BusinessObject.objects.create( name=source.name, process=source.process, description=source.description, content_type=source.content_type, object_id=new_object_id, ) # 同步 BusinessObject 的时间戳字段(保持一致性) models.BusinessObject.objects.filter(id=cloned.id).update( created_at=source.created_at, updated_at=source.updated_at, ) # 复制 state_logs(按完成时间排序,保证克隆后的序列稳定) source_logs = sorted( list(source.state_logs.all()), key=lambda log: (log.completed_at, log.id), ) for src_log in source_logs: new_log = models.StateFlowRecord.objects.create( business_object=cloned, state=src_log.state, completed_by=src_log.completed_by, is_cancelled=src_log.is_cancelled, cancelled_at=src_log.cancelled_at, ) # 同步 StateFlowRecord 的时间戳字段(含 completed_at) models.StateFlowRecord.objects.filter(id=new_log.id).update( completed_at=src_log.completed_at, created_at=src_log.created_at, updated_at=src_log.updated_at, cancelled_at=src_log.cancelled_at, ) # 复制参数记录(按 created_at 排序) src_param_records = sorted( list(src_log.parameter_records.all()), key=lambda rec: (rec.created_at, rec.id), ) for src_rec in src_param_records: new_rec = models.StateLogParameterRecord.objects.create( state_log=new_log, parameters=copy.deepcopy(src_rec.parameters), remark=src_rec.remark, ) models.StateLogParameterRecord.objects.filter(id=new_rec.id).update( created_at=src_rec.created_at, updated_at=src_rec.updated_at, ) # 重新加载,确保返回对象的字段与 DB 一致(尤其是时间戳) cloned.refresh_from_db() actual_object.refresh_from_db() actual_object.business_object_id = cloned.id actual_object.save() return cloned