forked from erp-dev/erp
9.1 KiB
9.1 KiB
Stateflow 状态流转系统
架构概述
Stateflow 是一个基于日志的订单状态流转管理系统,采用服务层模式实现业务逻辑与数据访问的分离。
核心概念
- State (状态): 流程中的一个节点,可以附带参数
- Process (流程模板): 由多个有序状态组成的流程定义
- Order (订单实例): 流程的具体执行实例
- OrderStateLog (完成日志): 记录订单完成每个状态的时间点和操作人
数据模型
State
State(name, parameters=[StateParameter(...)])
- 状态节点定义
- 支持附加参数(键值对)
Process
Process(name, state_nodes=ManyToMany[ProcessNode])
- 流程模板,定义状态流转顺序
- 通过
ProcessNode中间表关联State,支持排序
ProcessNode
ProcessNode(process, state, order)
- Process 和 State 的多对多关联表
order字段定义状态在流程中的执行顺序
Order
Order(process, metadata={})
- 流程的具体实例
- 不存储
current_state字段 - 当前状态通过查询
OrderStateLog推导
OrderStateLog
OrderStateLog(order, state, completed_at, completed_by, is_cancelled, cancelled_at)
- 记录订单完成某个状态的日志
completed_at: 完成时间completed_by: 操作人is_cancelled: 是否已撤销cancelled_at: 撤销时间- 唯一约束:
(order, state)- 每个状态只能有一条记录 - 不允许删除,重置进度时只标记为已撤销
核心逻辑
状态推导规则
当前状态的推导遵循以下规则:
- 未开始: 没有任何有效完成日志(或所有记录都被撤销) → 返回
None - 进行中: 有部分完成日志 → 返回下一个未完成的状态
- 已完成: 所有状态都已完成 → 返回
None
重要: 初始状态是 None(未开始),而不是第一个节点。
示例:
from stateflow.services import get_order_current_state, get_overall_status
# 获取当前状态(None = 未开始或已完成)
current_state = get_order_current_state(order)
# 获取整体状态类型
status = get_overall_status(order) # 'not_started', 'in_progress', 'completed'
状态推进
from stateflow.services import advance_to_next_state
# 推进到下一个状态(需要传入操作人)
success = advance_to_next_state(order, completed_by=request.user)
推进规则:
- 如果是初始状态(
None),推进到第一个节点 - 自动查找当前状态
- 验证是否可以推进(是否已完成)
- 创建
OrderStateLog记录完成时间和操作人 - 返回
(True, message)表示成功,(False, reason)表示失败
完整时间线
from stateflow.services import get_order_state_timeline
# 获取完整的状态时间线
timeline = get_order_state_timeline(order)
# [
# {'state': State1, 'completed_at': datetime, 'completed_by': User, 'is_completed': True},
# {'state': State2, 'completed_at': None, 'completed_by': None, 'is_completed': False},
# ...
# ]
服务层 API
所有业务逻辑封装在 stateflow/services.py 中,可在 Admin、API、任务队列等场景复用。
主要函数
get_order_current_state(order) -> State | None
获取订单当前应处理的状态。
get_overall_status(order) -> str
返回订单整体状态:
'not_started': 未开始'in_progress': 进行中'completed': 已完成
get_order_state_status(order, state) -> str
返回订单中某个状态的状态:
'not_started': 未开始'in_progress': 进行中'completed': 已完成
advance_to_next_state(order, completed_by) -> Tuple[bool, str]
推进到下一个状态,创建完成日志。返回 (是否成功, 消息)。
get_order_state_timeline(order) -> List[dict]
获取完整时间线,包含所有状态的完成情况(包括已撤销的记录)。
get_progress_percentage(order) -> float
计算完成进度百分比(0.0 - 100.0)。
reset_order_progress(order)
重置订单进度。注意:不删除日志记录,而是标记为已撤销并记录撤销时间,保留完整的操作历史。
can_advance_to_next_state(order) -> Tuple[bool, str]
检查是否可以继续推进。返回 (是否可以推进, 原因)。
使用示例
1. 创建流程模板
from stateflow.models import State, Process, ProcessNode
# 创建状态
pending = State.objects.create(name="待审核")
approved = State.objects.create(name="已审核")
shipped = State.objects.create(name="已发货")
# 创建流程
process = Process.objects.create(name="订单流程")
# 关联状态(设置顺序)
ProcessNode.objects.create(process=process, state=pending, order=1)
ProcessNode.objects.create(process=process, state=approved, order=2)
ProcessNode.objects.create(process=process, state=shipped, order=3)
2. 创建订单并推进
from stateflow.models import Order
from stateflow.services import get_order_current_state, advance_to_next_state, get_overall_status
# 创建订单实例
order = Order.objects.create(process=process, metadata={'order_no': 'SO-001'})
# 查看当前状态(初始状态是 None)
current = get_order_current_state(order) # None
status = get_overall_status(order) # 'not_started'
# 推进到第一个状态
success, msg = advance_to_next_state(order, completed_by=request.user)
current = get_order_current_state(order) # approved
# 推进到下一个状态
success, msg = advance_to_next_state(order, completed_by=request.user)
current = get_order_current_state(order) # shipped
3. 在 API 中使用
from rest_framework.decorators import action
from rest_framework.response import Response
from stateflow.services import advance_to_next_state, get_order_state_timeline
class OrderViewSet(viewsets.ModelViewSet):
@action(detail=True, methods=['post'])
def advance(self, request, pk=None):
order = self.get_object()
success, message = advance_to_next_state(order, completed_by=request.user)
if success:
return Response({'status': 'advanced', 'message': message})
return Response({'status': 'failed', 'message': message}, status=400)
@action(detail=True, methods=['get'])
def timeline(self, request, pk=None):
order = self.get_object()
timeline = get_order_state_timeline(order)
return Response(timeline)
Admin 配置
Admin 界面提供以下功能:
- State 管理: 创建和编辑状态,支持内联编辑参数
- Process 管理: 定义流程,通过内联表添加有序状态节点
- Order 管理:
- 查看完成日志(只读内联)
- 批量操作:推进到下一状态、重置进度
- 显示当前状态、完成百分比
- OrderStateLog 管理: 只读日志查看,用于审计
迁移历史
0001_initial.py: 初始模型(State, Process with CharField state_nodes)0002-0007: 其他应用迁移0008_process_state_nodes_remove_process_current_state...: 将 state_nodes 改为 ManyToMany,创建 ProcessNode 和 Order0009_remove_order_current_state_orderstatelog: 移除 Order.current_state,创建 OrderStateLog
测试
运行测试:
python manage.py test stateflow
测试覆盖:
- ✅ 初始状态推导
- ✅ 状态推进逻辑
- ✅ 状态类型判断
- ✅ 完整时间线生成
- ✅ 进度重置
设计优势
- 审计完整: 每次状态变更都有时间和操作人记录
- 数据一致: 状态从日志推导,无冗余存储
- 逻辑复用: 服务层可在 Admin/API/Task 中共享
- 灵活扩展: 可轻松添加状态前置条件、权限检查等
- 可追溯: 通过 OrderStateLog 实现完整的操作历史
- 历史保留: 重置进度时不删除记录,保留完整的操作痕迹
- 撤销追踪: 记录撤销时间,方便审计和问题追溯
ProcessNode vs OrderStateLog
很多人会混淆这两个表,这里做详细说明:
ProcessNode (流程模板)
- 作用: 定义流程包含哪些状态,以及执行顺序
- 关系: Process ↔ State 的多对多关联
- 层级: 模板层(一对多)
- 类比: "菜谱",定义做菜的步骤
- 示例: 订单流程 = [待审核(1) → 已审核(2) → 已发货(3)]
OrderStateLog (订单实例)
- 作用: 记录某个订单完成了哪些状态,以及完成时间、操作人
- 关系: Order → State 的完成记录
- 层级: 实例层(每个订单独立)
- 类比: "做菜记录",记录实际做了什么、何时做的
- 示例: 订单#001 在 2025-01-10 由张三完成了"待审核"状态
注意事项
- 唯一约束: 每个
(order, state)只能有一条记录 - 顺序性: 必须按 ProcessNode.order 顺序完成,不能跳过
- 操作人必填:
advance_to_next_state()必须传入completed_by参数 - 不可删除: OrderStateLog 不允许删除,只能标记为已撤销
- 保留历史: 重置进度时保留所有历史记录,并记录撤销时间
- 初始状态: 新订单的初始状态是
None(未开始),而不是第一个节点