""" 系統設定與日誌資料模型 """ from datetime import datetime from sqlalchemy import String, ForeignKey, Text, JSON, Enum as SQLEnum, Index from sqlalchemy.orm import Mapped, mapped_column, relationship from typing import Optional, TYPE_CHECKING import enum from app.db.session import Base if TYPE_CHECKING: from app.models.user import User from app.models.report import Report class SettingType(str, enum.Enum): """設定值類型""" STRING = "string" NUMBER = "number" BOOLEAN = "boolean" JSON = "json" class NotificationType(str, enum.Enum): """通知類型""" EMAIL = "email" SYSTEM = "system" class NotificationStatus(str, enum.Enum): """通知狀態""" PENDING = "pending" SENT = "sent" FAILED = "failed" class SystemSetting(Base): """系統設定表""" __tablename__ = "system_settings" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) setting_key: Mapped[str] = mapped_column(String(50), unique=True, nullable=False, comment="設定鍵") setting_value: Mapped[Optional[str]] = mapped_column(Text, comment="設定值") setting_type: Mapped[SettingType] = mapped_column(SQLEnum(SettingType), default=SettingType.STRING) description: Mapped[Optional[str]] = mapped_column(String(200), comment="設定描述") updated_by: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id"), comment="更新者ID") updated_at: Mapped[datetime] = mapped_column(default=datetime.utcnow, onupdate=datetime.utcnow) def get_value(self): """取得轉換後的設定值""" if self.setting_value is None: return None if self.setting_type == SettingType.NUMBER: return float(self.setting_value) if '.' in self.setting_value else int(self.setting_value) if self.setting_type == SettingType.BOOLEAN: return self.setting_value.lower() in ('true', '1', 'yes') if self.setting_type == SettingType.JSON: import json return json.loads(self.setting_value) return self.setting_value class AuditLog(Base): """操作日誌表""" __tablename__ = "audit_logs" __table_args__ = ( Index("idx_audit_user", "user_id"), Index("idx_audit_action", "action"), Index("idx_audit_created", "created_at"), ) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) user_id: Mapped[Optional[int]] = mapped_column(ForeignKey("users.id"), comment="操作用戶ID") action: Mapped[str] = mapped_column(String(50), nullable=False, comment="操作類型") target_type: Mapped[Optional[str]] = mapped_column(String(50), comment="目標類型") target_id: Mapped[Optional[str]] = mapped_column(String(50), comment="目標ID") details: Mapped[Optional[dict]] = mapped_column(JSON, comment="操作詳情") ip_address: Mapped[Optional[str]] = mapped_column(String(45), comment="IP地址") user_agent: Mapped[Optional[str]] = mapped_column(String(500), comment="User Agent") created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) class NotificationLog(Base): """通知記錄表""" __tablename__ = "notification_logs" __table_args__ = ( Index("idx_notification_status", "status"), ) id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False) report_id: Mapped[Optional[int]] = mapped_column(ForeignKey("reports.id"), comment="關聯報告ID") notification_type: Mapped[NotificationType] = mapped_column(SQLEnum(NotificationType), default=NotificationType.EMAIL) subject: Mapped[Optional[str]] = mapped_column(String(200), comment="通知標題") content: Mapped[Optional[str]] = mapped_column(Text, comment="通知內容") status: Mapped[NotificationStatus] = mapped_column(SQLEnum(NotificationStatus), default=NotificationStatus.PENDING) sent_at: Mapped[Optional[datetime]] = mapped_column() error_message: Mapped[Optional[str]] = mapped_column(Text) created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) # 關聯 report: Mapped[Optional["Report"]] = relationship(back_populates="notifications")