"""管理会计OS 数据模型""" from sqlalchemy import Column, Integer, String, Text, Float, DateTime, ForeignKey, Boolean, JSON, func from app.database import Base from app.models.budget_plan import BudgetPlan from app.models.cost_model import StandardCost, ActualCost, AbcActivity, AbcAllocation class User(Base): """用户""" __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String(50), unique=True, nullable=False) password_hash = Column(String(128), nullable=False) name = Column(String(100), nullable=False) role = Column(String(20), default="finance") # ceo / finance / business / it phone = Column(String(20), nullable=True) created_at = Column(DateTime, server_default=func.now()) class StrategicMap(Base): """战略地图""" __tablename__ = "strategic_maps" id = Column(Integer, primary_key=True, index=True) title = Column(String(200), nullable=False, comment="地图名称") version = Column(String(20), default="v1.0", comment="版本号") status = Column(String(20), default="draft", comment="draft/published") dimensions = Column(JSON, nullable=True, comment="四维度和目标列表") canvas_data = Column(JSON, nullable=True, comment="画布连线数据") created_by = Column(Integer, nullable=True) created_at = Column(DateTime, server_default=func.now()) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) class KPIDefinition(Base): """KPI字典""" __tablename__ = "kpi_definitions" id = Column(Integer, primary_key=True, index=True) map_id = Column(Integer, ForeignKey("strategic_maps.id"), nullable=True, comment="关联战略地图") kpi_code = Column(String(50), unique=True, nullable=False, comment="KPI编码") kpi_name = Column(String(200), nullable=False, comment="KPI名称") dimension = Column(String(50), comment="所属维度: finance/customer/process/learning") objective = Column(String(200), comment="关联战略目标") formula = Column(Text, nullable=True, comment="计算公式") formula_desc = Column(String(500), nullable=True, comment="公式说明") data_source_type = Column(String(20), default="manual", comment="erp/business/excel/manual") data_source_config = Column(JSON, nullable=True, comment="数据源配置") frequency = Column(String(20), default="monthly", comment="daily/weekly/monthly/quarterly/yearly") unit = Column(String(50), default="%", comment="单位") target_value = Column(Float, nullable=True, comment="目标值") threshold_green = Column(String(100), nullable=True, comment="绿灯阈值") threshold_yellow = Column(String(100), nullable=True, comment="黄灯阈值") threshold_red = Column(String(100), nullable=True, comment="红灯阈值") category = Column(String(50), nullable=True, comment="BSC二级类别: revenue_growth/profitability/cost_control/asset_efficiency/cash_risk/customer_scale/customer_concentration/customer_satisfaction/supply_chain/delivery_quality/talent_pipeline/employee_engagement/innovation") responsible_dept = Column(String(200), nullable=True, comment="负责部门") responsible_user = Column(String(100), nullable=True, comment="负责人") status = Column(String(20), default="active") epic = Column(String(50), default="Epic2", comment="所属Epic") created_by = Column(Integer, nullable=True) created_at = Column(DateTime, server_default=func.now()) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) class KPIValue(Base): """KPI实际值""" __tablename__ = "kpi_values" id = Column(Integer, primary_key=True, index=True) kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False) period = Column(String(20), nullable=False, comment="期间 2026-05") actual_value = Column(Float, nullable=True, comment="实际值") source_type = Column(String(20), default="manual", comment="erp/excel/manual") source_batch = Column(String(100), nullable=True, comment="导入批次号") data_status = Column(String(20), default="pending", comment="pending/verified/error") calculated_at = Column(DateTime, server_default=func.now()) remark = Column(String(500), nullable=True) class DataSourceConfig(Base): """数据源配置""" __tablename__ = "data_source_config" id = Column(Integer, primary_key=True, index=True) name = Column(String(200), nullable=False, comment="数据源名称") source_type = Column(String(20), nullable=False, comment="erp/business/excel") api_endpoint = Column(String(500), nullable=True, comment="API地址") api_key = Column(String(200), nullable=True, comment="API Key") query_sql = Column(Text, nullable=True, comment="SQL查询语句") sync_type = Column(String(20), default="realtime", comment="realtime/batch") status = Column(String(20), default="active") last_sync_at = Column(DateTime, nullable=True) created_at = Column(DateTime, server_default=func.now()) class KPIAlert(Base): """预警记录""" __tablename__ = "kpi_alerts" id = Column(Integer, primary_key=True, index=True) kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False) kpi_value_id = Column(Integer, ForeignKey("kpi_values.id"), nullable=True) alert_level = Column(String(20), default="yellow", comment="green/yellow/red") alert_message = Column(String(500), nullable=False) status = Column(String(20), default="pending", comment="pending/processing/resolved") assignee = Column(String(100), nullable=True, comment="处理人") resolution = Column(Text, nullable=True, comment="处理结果") resolved_at = Column(DateTime, nullable=True) created_at = Column(DateTime, server_default=func.now()) class OperationLog(Base): """操作日志""" __tablename__ = "operation_logs" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, nullable=True) action = Column(String(50), nullable=False, comment="create/update/delete/calculate/import") target_type = Column(String(50), nullable=False, comment="kpi/map/alert/source") target_id = Column(Integer, nullable=True) detail = Column(JSON, nullable=True) created_at = Column(DateTime, server_default=func.now()) class NotificationChannel(Base): """通知渠道配置""" __tablename__ = "notification_channels" id = Column(Integer, primary_key=True, index=True) name = Column(String(100), nullable=False, comment="渠道名称") channel_type = Column(String(30), nullable=False, comment="wecom/mail/sms") config = Column(JSON, nullable=True, comment="渠道配置") enabled = Column(Boolean, default=True) created_at = Column(DateTime, server_default=func.now()) class NotificationLog(Base): """通知发送日志""" __tablename__ = "notification_logs" id = Column(Integer, primary_key=True, index=True) alert_id = Column(Integer, ForeignKey("kpi_alerts.id"), nullable=True) channel = Column(String(30), nullable=False, comment="wecom/mail") recipient = Column(String(200), nullable=True, comment="收件人") title = Column(String(200), nullable=True) content = Column(Text, nullable=True) status = Column(String(20), default="pending", comment="pending/sent/failed") error_msg = Column(String(500), nullable=True) sent_at = Column(DateTime, nullable=True) created_at = Column(DateTime, server_default=func.now()) class RolePermission(Base): """角色权限配置(单条记录,key-value)""" __tablename__ = "role_permissions" id = Column(Integer, primary_key=True, index=True) key = Column(String(50), unique=True, nullable=False, comment="配置键: route_permissions / action_permissions") value = Column(JSON, nullable=False, comment="配置值") updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) class ActionPlan(Base): """改善行动计划""" __tablename__ = "action_plans" id = Column(Integer, primary_key=True, index=True) alert_id = Column(Integer, ForeignKey("kpi_alerts.id"), nullable=True, comment="关联预警") kpi_id = Column(Integer, ForeignKey("kpi_definitions.id"), nullable=False, comment="关联KPI") title = Column(String(200), nullable=False, comment="计划标题") description = Column(Text, nullable=True, comment="详细描述") assignee = Column(String(100), nullable=True, comment="负责人") priority = Column(String(20), default="medium", comment="high/medium/low") due_date = Column(DateTime, nullable=True, comment="截止日期") status = Column(String(20), default="pending", comment="pending/in_progress/completed/cancelled") progress = Column(Integer, default=0, comment="完成进度 0-100") result = Column(Text, nullable=True, comment="改善结果") created_by = Column(String(100), nullable=True, comment="创建人") created_at = Column(DateTime, server_default=func.now()) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) class OrgNode(Base): """组织节点: 集团→事业部→区域→部门→班组 5级""" __tablename__ = "org_nodes" id = Column(Integer, primary_key=True, index=True) parent_id = Column(Integer, ForeignKey("org_nodes.id"), nullable=True, comment="父节点ID") name = Column(String(100), nullable=False, comment="节点名称") code = Column(String(50), unique=True, nullable=True, comment="编码") level = Column(Integer, nullable=False, comment="1=集团 2=事业部 3=区域 4=部门 5=班组") sort_order = Column(Integer, default=0, comment="排序") enabled = Column(Integer, default=1, comment="1启用 0禁用") path = Column(String(500), nullable=True, comment="路径") remark = Column(String(200), nullable=True, comment="备注") created_at = Column(DateTime, server_default=func.now()) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now()) class StrategicMapVersion(Base): """战略地图版本快照""" __tablename__ = "strategic_map_versions" id = Column(Integer, primary_key=True, index=True) map_id = Column(Integer, ForeignKey("strategic_maps.id", ondelete="CASCADE"), nullable=False, comment="关联地图") version = Column(String(20), nullable=False, comment="版本号 v1.0 v1.1 ...") dimensions = Column(JSON, nullable=False, comment="维度数据快照") canvas_data = Column(JSON, nullable=False, comment="画布数据快照") comment = Column(String(500), nullable=True, comment="说明") created_by = Column(Integer, nullable=True) created_at = Column(DateTime, server_default=func.now()) class MapObjective(Base): """战略地图目标: 每个维度下的具体目标""" __tablename__ = "map_objectives" id = Column(Integer, primary_key=True, index=True) map_id = Column(Integer, ForeignKey("strategic_maps.id", ondelete="CASCADE"), nullable=False, comment="关联地图") dimension_key = Column(String(50), nullable=False, comment="所属维度: finance/customer/process/learning") name = Column(String(200), nullable=False, comment="目标名称") description = Column(Text, nullable=True, comment="描述") icon = Column(String(50), default="target", comment="图标标识") sort_order = Column(Integer, default=0, comment="排序") created_at = Column(DateTime, server_default=func.now()) updated_at = Column(DateTime, server_default=func.now(), onupdate=func.now())