239 lines
9.4 KiB
Python
239 lines
9.4 KiB
Python
"""数据库模型 —— 所有表定义"""
|
||
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")
|