2026-02-12 14:24:05 +08:00

239 lines
9.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""数据库模型 —— 所有表定义"""
from sqlalchemy import (
Column, Integer, String, Float, Date, DateTime, Text,
ForeignKey, Enum as SAEnum, JSON
)
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from database import Base
import enum
# ──────────────────────────── 枚举定义 ────────────────────────────
class ProjectType(str, enum.Enum):
CLIENT_FORMAL = "客户正式项目"
CLIENT_TEST = "客户测试项目"
INTERNAL_ORIGINAL = "内部原创项目"
INTERNAL_TEST = "内部测试项目"
class ProjectStatus(str, enum.Enum):
IN_PROGRESS = "制作中"
COMPLETED = "已完成"
class PhaseGroup(str, enum.Enum):
PRE = "前期"
PRODUCTION = "制作"
POST = "后期"
class UserRole(str, enum.Enum):
MEMBER = "成员"
LEADER = "组长"
SUPERVISOR = "主管"
OWNER = "Owner"
class WorkType(str, enum.Enum):
PRODUCTION = "制作"
TEST = "测试"
PLAN = "方案"
class ContentType(str, enum.Enum):
ANIMATION = "内容制作"
DESIGN = "设定策划"
EDITING = "剪辑后期"
OTHER = "其他"
class SubmitTo(str, enum.Enum):
LEADER = "组长"
PRODUCER = "制片"
INTERNAL = "内部"
EXTERNAL = "外部"
class SubscriptionPeriod(str, enum.Enum):
MONTHLY = ""
YEARLY = ""
class CostAllocationType(str, enum.Enum):
PROJECT = "指定项目"
TEAM = "内容组整体"
MANUAL = "手动分摊"
class OutsourceType(str, enum.Enum):
ANIMATION = "动画"
EDITING = "剪辑"
FULL_EPISODE = "整集"
# ──────────────────────────── 用户 ────────────────────────────
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String(50), unique=True, nullable=False, index=True)
password_hash = Column(String(255), nullable=False)
name = Column(String(50), nullable=False)
phase_group = Column(SAEnum(PhaseGroup), nullable=False)
role = Column(SAEnum(UserRole), nullable=False, default=UserRole.MEMBER)
monthly_salary = Column(Float, nullable=False, default=0)
is_active = Column(Integer, nullable=False, default=1)
created_at = Column(DateTime, server_default=func.now())
# 关系
submissions = relationship("Submission", back_populates="user")
led_projects = relationship("Project", back_populates="leader")
@property
def daily_cost(self):
"""日成本 = 月薪 ÷ 22"""
from config import WORKING_DAYS_PER_MONTH
return round(self.monthly_salary / WORKING_DAYS_PER_MONTH, 2) if self.monthly_salary else 0
# ──────────────────────────── 项目 ────────────────────────────
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), nullable=False)
project_type = Column(SAEnum(ProjectType), nullable=False)
status = Column(SAEnum(ProjectStatus), nullable=False, default=ProjectStatus.IN_PROGRESS)
leader_id = Column(Integer, ForeignKey("users.id"), nullable=True)
current_phase = Column(SAEnum(PhaseGroup), nullable=False, default=PhaseGroup.PRE)
episode_duration_minutes = Column(Float, nullable=False)
episode_count = Column(Integer, nullable=False)
estimated_completion_date = Column(Date, nullable=True)
actual_completion_date = Column(Date, nullable=True)
contract_amount = Column(Float, nullable=True) # 仅客户正式项目
created_at = Column(DateTime, server_default=func.now())
# 关系
leader = relationship("User", back_populates="led_projects")
submissions = relationship("Submission", back_populates="project")
outsource_costs = relationship("OutsourceCost", back_populates="project")
ai_tool_allocations = relationship("AIToolCostAllocation", back_populates="project")
@property
def target_total_seconds(self):
"""目标总秒数 = 单集时长(分) × 60 × 集数"""
return int(self.episode_duration_minutes * 60 * self.episode_count)
# ──────────────────────────── 内容提交 ────────────────────────────
class Submission(Base):
__tablename__ = "submissions"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
project_phase = Column(SAEnum(PhaseGroup), nullable=False)
work_type = Column(SAEnum(WorkType), nullable=False)
content_type = Column(SAEnum(ContentType), nullable=False)
duration_minutes = Column(Float, nullable=True, default=0)
duration_seconds = Column(Float, nullable=True, default=0)
total_seconds = Column(Float, nullable=False, default=0) # 系统自动计算
hours_spent = Column(Float, nullable=True) # 可选:投入时长(小时)
submit_to = Column(SAEnum(SubmitTo), nullable=False)
description = Column(Text, nullable=True)
submit_date = Column(Date, nullable=False)
created_at = Column(DateTime, server_default=func.now())
# 关系
user = relationship("User", back_populates="submissions")
project = relationship("Project", back_populates="submissions")
history = relationship("SubmissionHistory", back_populates="submission")
# ──────────────────────────── AI 工具成本 ────────────────────────────
class AIToolCost(Base):
__tablename__ = "ai_tool_costs"
id = Column(Integer, primary_key=True, index=True)
tool_name = Column(String(100), nullable=False)
subscription_period = Column(SAEnum(SubscriptionPeriod), nullable=False)
amount = Column(Float, nullable=False)
allocation_type = Column(SAEnum(CostAllocationType), nullable=False)
project_id = Column(Integer, ForeignKey("projects.id"), nullable=True) # 指定项目时
recorded_by = Column(Integer, ForeignKey("users.id"), nullable=False)
record_date = Column(Date, nullable=False)
created_at = Column(DateTime, server_default=func.now())
# 关系
allocations = relationship("AIToolCostAllocation", back_populates="ai_tool_cost")
class AIToolCostAllocation(Base):
"""AI 工具成本手动分摊明细"""
__tablename__ = "ai_tool_cost_allocations"
id = Column(Integer, primary_key=True, index=True)
ai_tool_cost_id = Column(Integer, ForeignKey("ai_tool_costs.id"), nullable=False)
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
percentage = Column(Float, nullable=False) # 0-100
ai_tool_cost = relationship("AIToolCost", back_populates="allocations")
project = relationship("Project", back_populates="ai_tool_allocations")
# ──────────────────────────── 外包成本 ────────────────────────────
class OutsourceCost(Base):
__tablename__ = "outsource_costs"
id = Column(Integer, primary_key=True, index=True)
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
outsource_type = Column(SAEnum(OutsourceType), nullable=False)
episode_start = Column(Integer, nullable=True)
episode_end = Column(Integer, nullable=True)
amount = Column(Float, nullable=False)
recorded_by = Column(Integer, ForeignKey("users.id"), nullable=False)
record_date = Column(Date, nullable=False)
created_at = Column(DateTime, server_default=func.now())
project = relationship("Project", back_populates="outsource_costs")
# ──────────────────────────── 人力成本手动调整 ────────────────────────────
class CostOverride(Base):
"""管理员手动修改某人某天的成本分摊"""
__tablename__ = "cost_overrides"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
date = Column(Date, nullable=False)
project_id = Column(Integer, ForeignKey("projects.id"), nullable=False)
override_amount = Column(Float, nullable=False)
adjusted_by = Column(Integer, ForeignKey("users.id"), nullable=False)
reason = Column(Text, nullable=True)
created_at = Column(DateTime, server_default=func.now())
# ──────────────────────────── 提交历史版本 ────────────────────────────
class SubmissionHistory(Base):
"""内容提交的修改历史"""
__tablename__ = "submission_history"
id = Column(Integer, primary_key=True, index=True)
submission_id = Column(Integer, ForeignKey("submissions.id"), nullable=False)
changed_by = Column(Integer, ForeignKey("users.id"), nullable=False)
change_reason = Column(Text, nullable=False)
old_data = Column(JSON, nullable=False)
new_data = Column(JSON, nullable=False)
created_at = Column(DateTime, server_default=func.now())
submission = relationship("Submission", back_populates="history")