363 lines
15 KiB
Python
363 lines
15 KiB
Python
"""审批流程模型 — 支持多级、并行、条件审批、版本控制与执行日志。"""
|
|
import json
|
|
from sqlalchemy import (
|
|
Column, Integer, String, Text, Boolean, DateTime, ForeignKey, JSON, func, UniqueConstraint
|
|
)
|
|
from sqlalchemy.orm import relationship
|
|
|
|
from app.core.database import Base
|
|
|
|
|
|
class WorkflowDefinition(Base):
|
|
"""审批流程定义模板。"""
|
|
|
|
__tablename__ = "workflow_definitions"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
name = Column(String(100), nullable=False)
|
|
description = Column(Text, nullable=True)
|
|
category = Column(String(50), default="ticket") # ticket / query / instance / general
|
|
is_default = Column(Boolean, default=False)
|
|
is_active = Column(Boolean, default=True)
|
|
created_at = Column(DateTime, server_default=func.now())
|
|
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
|
created_by = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# 流程级配置(JSON格式,灵活扩展)
|
|
# ═══════════════════════════════════════════════════════════
|
|
|
|
# 全局流程配置:
|
|
# {
|
|
# "timeout_strategy": "auto_approve|auto_reject|escalate|notify",
|
|
# "timeout_hours": 48,
|
|
# "escalation_user_id": 1,
|
|
# "allow_transfer": true,
|
|
# "allow_skip": false,
|
|
# "require_comment": true,
|
|
# "parallel_mode": "all|any", // 并行审批模式:全部通过/任一通过
|
|
# "notify_channels": ["email", "sms", "webhook"]
|
|
# }
|
|
flow_config = Column(JSON, nullable=True, default=dict)
|
|
|
|
# 触发条件配置(支持多条件组合):
|
|
# {
|
|
# "conditions": [
|
|
# {"field": "ticket.type", "operator": "in", "value": ["DDL", "DML"]},
|
|
# {"field": "ticket.risk_level", "operator": ">=", "value": "high"},
|
|
# {"field": "database.instance_id", "operator": "==", "value": 1}
|
|
# ],
|
|
# "logic": "AND|OR"
|
|
# }
|
|
trigger_conditions = Column(JSON, nullable=True)
|
|
|
|
# 步骤定义关联
|
|
steps = relationship(
|
|
"WorkflowStep",
|
|
back_populates="workflow",
|
|
order_by="WorkflowStep.step_number",
|
|
cascade="all, delete-orphan"
|
|
)
|
|
|
|
# 版本关联
|
|
versions = relationship(
|
|
"WorkflowVersion",
|
|
back_populates="workflow",
|
|
order_by="desc(WorkflowVersion.version_number)",
|
|
cascade="all, delete-orphan"
|
|
)
|
|
|
|
# 审批实例关联
|
|
approvals = relationship(
|
|
"TicketApproval",
|
|
back_populates="workflow_def",
|
|
lazy="dynamic"
|
|
)
|
|
|
|
# 执行日志关联
|
|
execution_logs = relationship(
|
|
"WorkflowExecutionLog",
|
|
back_populates="workflow",
|
|
lazy="dynamic",
|
|
order_by="desc(WorkflowExecutionLog.created_at)"
|
|
)
|
|
|
|
def get_current_version(self):
|
|
"""获取当前激活的版本。"""
|
|
return next((v for v in self.versions if v.is_active), None)
|
|
|
|
def to_dict(self, include_steps: bool = False) -> dict:
|
|
data = {
|
|
"id": self.id,
|
|
"name": self.name,
|
|
"description": self.description,
|
|
"category": self.category,
|
|
"is_default": self.is_default,
|
|
"is_active": self.is_active,
|
|
"flow_config": self.flow_config,
|
|
"trigger_conditions": self.trigger_conditions,
|
|
"step_count": len(self.steps) if self.steps else 0,
|
|
"version_count": len(self.versions) if self.versions else 0,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
}
|
|
if include_steps:
|
|
data["steps"] = [s.to_dict() for s in (self.steps or [])]
|
|
return data
|
|
|
|
|
|
class WorkflowVersion(Base):
|
|
"""审批流程版本控制 — 支持流程的版本管理与回溯。"""
|
|
|
|
__tablename__ = "workflow_versions"
|
|
__table_args__ = (UniqueConstraint("workflow_id", "version_number", name="uix_workflow_version"),)
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
workflow_id = Column(Integer, ForeignKey("workflow_definitions.id", ondelete="CASCADE"), nullable=False)
|
|
version_number = Column(Integer, nullable=False) # 版本号,递增
|
|
version_name = Column(String(100), nullable=True) # 版本名称,如 "v2.0-添加并行审批"
|
|
is_active = Column(Boolean, default=False) # 是否为当前激活版本
|
|
|
|
# 版本快照 — 存储此版本完整的流程定义快照
|
|
steps_snapshot = Column(JSON, nullable=False)
|
|
flow_config_snapshot = Column(JSON, nullable=True)
|
|
|
|
# 变更说明
|
|
change_summary = Column(Text, nullable=True)
|
|
created_by = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
created_at = Column(DateTime, server_default=func.now())
|
|
|
|
# 关联
|
|
workflow = relationship("WorkflowDefinition", back_populates="versions")
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"id": self.id,
|
|
"workflow_id": self.workflow_id,
|
|
"version_number": self.version_number,
|
|
"version_name": self.version_name,
|
|
"is_active": self.is_active,
|
|
"change_summary": self.change_summary,
|
|
"steps_count": len(self.steps_snapshot) if self.steps_snapshot else 0,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
}
|
|
|
|
|
|
class WorkflowStep(Base):
|
|
"""审批步骤定义 — 支持灵活的条件判断与动态审批人分配。"""
|
|
|
|
__tablename__ = "workflow_steps"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
workflow_id = Column(Integer, ForeignKey("workflow_definitions.id", ondelete="CASCADE"), nullable=False)
|
|
step_number = Column(Integer, nullable=False)
|
|
name = Column(String(100), nullable=False)
|
|
description = Column(Text, nullable=True)
|
|
|
|
# 步骤类型: serial(串行) / parallel(并行-会签) / any_one(或签) / condition(条件分支)
|
|
step_type = Column(String(20), nullable=False, default="serial")
|
|
|
|
# 审批人角色
|
|
approver_role = Column(String(20), nullable=True) # admin / dba / manager / creator
|
|
|
|
# 指定审批人ID列表(JSON数组)
|
|
approver_ids = Column(JSON, nullable=True)
|
|
|
|
# 审批人分配策略:
|
|
# {
|
|
# "type": "role|specific|leader|load_balance|creator|expression",
|
|
# "expression": "${ticket.creator.manager_id}", // 动态表达式
|
|
# "fallback_user_id": 1, // 无法解析时的 fallback
|
|
# "department_id": 2 // 指定部门
|
|
# }
|
|
approver_assignment = Column(JSON, nullable=True, default=dict)
|
|
|
|
is_required = Column(Boolean, default=True)
|
|
timeout_hours = Column(Integer, default=24)
|
|
auto_approve_on_timeout = Column(Boolean, default=False)
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# 条件判断配置(条件步骤使用)
|
|
# ═══════════════════════════════════════════════════════════
|
|
|
|
# 步骤准入条件(满足条件才进入此步骤):
|
|
# {
|
|
# "conditions": [
|
|
# {"field": "ticket.sql_type", "operator": "in", "value": ["ALTER", "DROP"]},
|
|
# {"field": "affected_rows", "operator": ">", "value": 1000}
|
|
# ],
|
|
# "logic": "AND"
|
|
# }
|
|
entry_conditions = Column(JSON, nullable=True)
|
|
|
|
# 步骤流转条件(满足条件才进入下一步,否则跳过后续步骤):
|
|
# {
|
|
# "conditions": [
|
|
# {"field": "current.result", "operator": "==", "value": "approved"}
|
|
# ],
|
|
# "on_false_action": "skip|reject|goto_step",
|
|
# "goto_step_number": 3
|
|
# }
|
|
transition_conditions = Column(JSON, nullable=True)
|
|
|
|
# ═══════════════════════════════════════════════════════════
|
|
# 字段权限配置
|
|
# ═══════════════════════════════════════════════════════════
|
|
|
|
# 此步骤中可查看/编辑的字段:
|
|
# {
|
|
# "visible_fields": ["ticket.title", "ticket.sql", "ticket.description"],
|
|
# "editable_fields": ["ticket.comment"],
|
|
# "masked_fields": ["ticket.password", "ticket.secret_key"]
|
|
# }
|
|
field_permissions = Column(JSON, nullable=True)
|
|
|
|
workflow = relationship("WorkflowDefinition", back_populates="steps")
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"id": self.id,
|
|
"workflow_id": self.workflow_id,
|
|
"step_number": self.step_number,
|
|
"name": self.name,
|
|
"description": self.description,
|
|
"step_type": self.step_type,
|
|
"approver_role": self.approver_role,
|
|
"approver_ids": self.approver_ids,
|
|
"approver_assignment": self.approver_assignment,
|
|
"is_required": self.is_required,
|
|
"timeout_hours": self.timeout_hours,
|
|
"auto_approve_on_timeout": self.auto_approve_on_timeout,
|
|
"entry_conditions": self.entry_conditions,
|
|
"transition_conditions": self.transition_conditions,
|
|
"field_permissions": self.field_permissions,
|
|
}
|
|
|
|
|
|
class TicketApproval(Base):
|
|
"""工单审批实例(运行时记录)。"""
|
|
|
|
__tablename__ = "ticket_approvals"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
ticket_id = Column(Integer, ForeignKey("tickets.id"), nullable=False, index=True)
|
|
|
|
# 关联的流程定义和版本
|
|
workflow_id = Column(Integer, ForeignKey("workflow_definitions.id"), nullable=True)
|
|
version_id = Column(Integer, ForeignKey("workflow_versions.id"), nullable=True)
|
|
|
|
step_number = Column(Integer, nullable=False)
|
|
step_name = Column(String(100), nullable=True)
|
|
step_type = Column(String(20), default="serial") # serial / parallel / any_one / condition
|
|
|
|
# 审批人信息
|
|
approver_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
approver_role = Column(String(20), nullable=True)
|
|
|
|
# 实际执行人(转交场景)
|
|
actual_approver_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
|
|
# 审批动作: pending / approved / rejected / transferred / skipped / auto_approved
|
|
action = Column(String(20), default="pending", index=True)
|
|
comment = Column(Text, nullable=True)
|
|
|
|
# 转交信息
|
|
transfer_from = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
transfer_reason = Column(Text, nullable=True)
|
|
|
|
# 审批时间记录
|
|
created_at = Column(DateTime, server_default=func.now())
|
|
updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())
|
|
completed_at = Column(DateTime, nullable=True)
|
|
|
|
# 超时标记
|
|
is_timeout = Column(Boolean, default=False)
|
|
timeout_handled_at = Column(DateTime, nullable=True)
|
|
|
|
# 关联
|
|
workflow_def = relationship("WorkflowDefinition", back_populates="approvals")
|
|
approver = relationship("User", foreign_keys=[approver_id])
|
|
actual_approver = relationship("User", foreign_keys=[actual_approver_id])
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"id": self.id,
|
|
"ticket_id": self.ticket_id,
|
|
"workflow_id": self.workflow_id,
|
|
"step_number": self.step_number,
|
|
"step_name": self.step_name,
|
|
"step_type": self.step_type,
|
|
"approver_id": self.approver_id,
|
|
"approver_role": self.approver_role,
|
|
"approver_name": self.approver.display_name if self.approver else None,
|
|
"actual_approver_id": self.actual_approver_id,
|
|
"action": self.action,
|
|
"comment": self.comment,
|
|
"transfer_from": self.transfer_from,
|
|
"transfer_reason": self.transfer_reason,
|
|
"is_timeout": self.is_timeout,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
|
"completed_at": self.completed_at.isoformat() if self.completed_at else None,
|
|
}
|
|
|
|
|
|
class WorkflowExecutionLog(Base):
|
|
"""流程执行日志 — 记录审批流程的完整执行轨迹,用于审计和问题排查。"""
|
|
|
|
__tablename__ = "workflow_execution_logs"
|
|
|
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
|
workflow_id = Column(Integer, ForeignKey("workflow_definitions.id"), nullable=False, index=True)
|
|
ticket_id = Column(Integer, ForeignKey("tickets.id"), nullable=True, index=True)
|
|
step_id = Column(Integer, ForeignKey("workflow_steps.id"), nullable=True)
|
|
approval_id = Column(Integer, ForeignKey("ticket_approvals.id"), nullable=True)
|
|
|
|
# 事件类型:
|
|
# workflow_started / step_entered / step_completed / approved / rejected /
|
|
# transferred / skipped / timeout / escalated / workflow_completed / workflow_cancelled
|
|
event_type = Column(String(50), nullable=False, index=True)
|
|
|
|
# 事件详情
|
|
event_description = Column(Text, nullable=True)
|
|
|
|
# 执行人
|
|
actor_id = Column(Integer, ForeignKey("users.id"), nullable=True)
|
|
actor_name = Column(String(100), nullable=True)
|
|
|
|
# 上下文数据(快照):
|
|
# {
|
|
# "step_snapshot": {...},
|
|
# "approval_snapshot": {...},
|
|
# "decision_context": {...},
|
|
# "condition_results": [...]
|
|
# }
|
|
context_data = Column(JSON, nullable=True)
|
|
|
|
created_at = Column(DateTime, server_default=func.now(), index=True)
|
|
|
|
# 关联
|
|
workflow = relationship("WorkflowDefinition", back_populates="execution_logs")
|
|
|
|
def to_dict(self) -> dict:
|
|
return {
|
|
"id": self.id,
|
|
"workflow_id": self.workflow_id,
|
|
"ticket_id": self.ticket_id,
|
|
"step_id": self.step_id,
|
|
"approval_id": self.approval_id,
|
|
"event_type": self.event_type,
|
|
"event_description": self.event_description,
|
|
"actor_id": self.actor_id,
|
|
"actor_name": self.actor_name,
|
|
"context_data": self.context_data,
|
|
"created_at": self.created_at.isoformat() if self.created_at else None,
|
|
}
|
|
|
|
def __repr__(self) -> str:
|
|
return (
|
|
f"<WorkflowExecutionLog("
|
|
f"workflow={self.workflow_id}, ticket={self.ticket_id}, "
|
|
f"event='{self.event_type}', actor='{self.actor_name}')>"
|
|
)
|