99 lines
3.2 KiB
Python
99 lines
3.2 KiB
Python
"""订单状态机 (T4.1)
|
||
|
||
6 态状态机:pending → paid → serving → delivered → completed;
|
||
任何阶段可转入 refunded。completed / refunded 为终态。
|
||
|
||
非法转换抛 InvalidStateTransition;DAO 层捕获后回滚事务。
|
||
"""
|
||
|
||
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]
|