Files
gaokao-volunteer-system/data/orders/state_machine.py

99 lines
3.2 KiB
Python
Raw Normal View History

"""订单状态机 (T4.1)
6 态状态机pending paid serving delivered completed
任何阶段可转入 refundedcompleted / refunded 为终态
非法转换抛 InvalidStateTransitionDAO 层捕获后回滚事务
"""
from __future__ import annotations
from enum import Enum
class OrderStatus(str, Enum):
"""订单 6 态枚举(继承 str 以便直接写入 SQLite / JSON"""
PENDING = "pending"
PAID = "paid"
SERVING = "serving"
DELIVERED = "delivered"
COMPLETED = "completed"
REFUNDED = "refunded"
TERMINAL_STATUSES: frozenset[str] = frozenset(
{OrderStatus.COMPLETED.value, OrderStatus.REFUNDED.value}
)
# 合法状态转换:单向推进 + 任意阶段可退款
ALLOWED_TRANSITIONS: dict[str, frozenset[str]] = {
OrderStatus.PENDING.value: frozenset(
{OrderStatus.PAID.value, OrderStatus.REFUNDED.value}
),
OrderStatus.PAID.value: frozenset(
{OrderStatus.SERVING.value, OrderStatus.REFUNDED.value}
),
OrderStatus.SERVING.value: frozenset(
{OrderStatus.DELIVERED.value, OrderStatus.REFUNDED.value}
),
OrderStatus.DELIVERED.value: frozenset(
{OrderStatus.COMPLETED.value, OrderStatus.REFUNDED.value}
),
OrderStatus.COMPLETED.value: frozenset(),
OrderStatus.REFUNDED.value: frozenset(),
}
class InvalidStateTransition(ValueError):
"""非法状态转换时抛出。"""
def is_known_status(status: str) -> bool:
"""判断字符串是否为已定义的 6 态之一。"""
return status in {s.value for s in OrderStatus}
def is_terminal(status: str) -> bool:
"""判断状态是否为终态completed / refunded"""
return status in TERMINAL_STATUSES
def is_valid_transition(from_status: str, to_status: str) -> bool:
"""判断 from → to 是否为合法转换。
- 已知状态但非法转换 False
- 未知状态 False不抛异常DAO 层需先校验状态合法性
"""
if not (is_known_status(from_status) and is_known_status(to_status)):
return False
if from_status == to_status:
# 相同状态视为非法(避免无意义的状态写入历史表)
return False
return to_status in ALLOWED_TRANSITIONS[from_status]
def assert_valid_transition(from_status: str, to_status: str) -> None:
"""校验状态转换合法性,非法时抛 InvalidStateTransition。"""
if not is_known_status(from_status):
raise InvalidStateTransition(f"未知起始状态: {from_status!r}")
if not is_known_status(to_status):
raise InvalidStateTransition(f"未知目标状态: {to_status!r}")
if from_status == to_status:
raise InvalidStateTransition(
f"状态相同无需转换: {from_status!r}{to_status!r}"
)
if to_status not in ALLOWED_TRANSITIONS[from_status]:
allowed = sorted(ALLOWED_TRANSITIONS[from_status])
raise InvalidStateTransition(
f"非法状态转换: {from_status!r}{to_status!r}(允许: {allowed}"
)
def next_states(from_status: str) -> frozenset[str]:
"""返回 from_status 的所有合法下一状态(用于前端展示)。"""
if not is_known_status(from_status):
return frozenset()
return ALLOWED_TRANSITIONS[from_status]