- 项目详情:拆分"已提交"为"制作产出"(中期)和"后期产出"(后期按类型细分) - 进度百分比仅计算中期动画产出,EP集数进度只统计中期 - 新增"全集通用"(episode=0)选项,与具体集数互斥 - 概览卡片改为上4下1布局,后期产出独立全宽卡片展示明细 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
333 lines
9.9 KiB
Python
333 lines
9.9 KiB
Python
"""Pydantic 数据模型 —— API 请求/响应格式定义"""
|
|
from pydantic import BaseModel, Field
|
|
from typing import Optional, List
|
|
from datetime import date, datetime
|
|
|
|
|
|
# ──────────────────────────── 认证 ────────────────────────────
|
|
|
|
class LoginRequest(BaseModel):
|
|
username: str
|
|
password: str
|
|
|
|
|
|
class Token(BaseModel):
|
|
access_token: str
|
|
token_type: str = "bearer"
|
|
|
|
|
|
# ──────────────────────────── 用户 ────────────────────────────
|
|
|
|
class UserCreate(BaseModel):
|
|
username: str
|
|
password: str
|
|
name: str
|
|
phase_group: str # 前期/中期/后期
|
|
role_id: int
|
|
monthly_salary: float = 0
|
|
bonus: float = 0
|
|
social_insurance: float = 0
|
|
|
|
|
|
class UserUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
phase_group: Optional[str] = None
|
|
role_id: Optional[int] = None
|
|
monthly_salary: Optional[float] = None
|
|
bonus: Optional[float] = None
|
|
social_insurance: Optional[float] = None
|
|
is_active: Optional[int] = None
|
|
password: Optional[str] = None # 管理员重置密码
|
|
|
|
|
|
class ChangePasswordRequest(BaseModel):
|
|
old_password: str
|
|
new_password: str
|
|
|
|
|
|
class UserOut(BaseModel):
|
|
id: int
|
|
username: str
|
|
name: str
|
|
phase_group: str
|
|
role_id: int
|
|
role_name: str
|
|
permissions: List[str] = []
|
|
monthly_salary: float
|
|
bonus: float
|
|
social_insurance: float
|
|
monthly_total_cost: float
|
|
daily_cost: float
|
|
is_active: int
|
|
created_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ──────────────────────────── 项目 ────────────────────────────
|
|
|
|
class MilestoneOut(BaseModel):
|
|
id: int
|
|
name: str
|
|
phase: str
|
|
is_completed: bool
|
|
completed_at: Optional[datetime] = None
|
|
sort_order: int
|
|
estimated_days: Optional[int] = None
|
|
start_date: Optional[date] = None
|
|
actual_days: Optional[int] = None # 计算值
|
|
is_overdue: Optional[bool] = False # 计算值
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class MilestoneCreate(BaseModel):
|
|
name: str
|
|
phase: str # 前期/后期
|
|
estimated_days: Optional[int] = None
|
|
|
|
|
|
class MilestoneUpdate(BaseModel):
|
|
estimated_days: Optional[int] = None
|
|
start_date: Optional[date] = None
|
|
|
|
|
|
class ProjectCreate(BaseModel):
|
|
name: str
|
|
project_type: str
|
|
leader_id: Optional[int] = None
|
|
current_phase: str = "前期"
|
|
episode_duration_minutes: float
|
|
episode_count: int
|
|
estimated_completion_date: Optional[date] = None
|
|
contract_amount: Optional[float] = None
|
|
milestones: Optional[List[dict]] = None # [{"name", "phase", "sort_order"}]
|
|
|
|
|
|
class ProjectUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
project_type: Optional[str] = None
|
|
status: Optional[str] = None
|
|
leader_id: Optional[int] = None
|
|
current_phase: Optional[str] = None
|
|
episode_duration_minutes: Optional[float] = None
|
|
episode_count: Optional[int] = None
|
|
estimated_completion_date: Optional[date] = None
|
|
actual_completion_date: Optional[date] = None
|
|
contract_amount: Optional[float] = None
|
|
|
|
|
|
class ProjectOut(BaseModel):
|
|
id: int
|
|
name: str
|
|
project_type: str
|
|
status: str
|
|
leader_id: Optional[int] = None
|
|
leader_name: Optional[str] = None
|
|
current_phase: str
|
|
episode_duration_minutes: float
|
|
episode_count: int
|
|
target_total_seconds: int
|
|
estimated_completion_date: Optional[date] = None
|
|
actual_completion_date: Optional[date] = None
|
|
contract_amount: Optional[float] = None
|
|
created_at: Optional[datetime] = None
|
|
# 动态计算字段
|
|
total_submitted_seconds: Optional[float] = 0
|
|
animation_seconds: Optional[float] = 0 # 中期动画产出
|
|
post_production_seconds: Optional[float] = 0 # 后期产出合计
|
|
post_production_breakdown: Optional[List[dict]] = [] # [{"type":"剪辑","seconds":300}, ...]
|
|
progress_percent: Optional[float] = 0
|
|
waste_seconds: Optional[float] = 0
|
|
waste_hours: Optional[float] = 0
|
|
waste_rate: Optional[float] = 0
|
|
# 里程碑
|
|
milestones: Optional[List[MilestoneOut]] = []
|
|
phase_summary: Optional[dict] = None
|
|
current_stage: Optional[str] = None
|
|
# EP 集数进度
|
|
episode_progress: Optional[List[dict]] = []
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ──────────────────────────── 内容提交 ────────────────────────────
|
|
|
|
class SubmissionCreate(BaseModel):
|
|
project_id: int
|
|
project_phase: str
|
|
work_type: str
|
|
content_type: str
|
|
duration_minutes: Optional[float] = 0
|
|
duration_seconds: Optional[float] = 0
|
|
hours_spent: Optional[float] = None
|
|
submit_to: str
|
|
description: Optional[str] = None
|
|
submit_date: date
|
|
delay_reason: Optional[str] = None
|
|
episode_number: Optional[int] = None
|
|
episode_numbers: Optional[List[int]] = None # 批量多集提交
|
|
user_id: Optional[int] = None # 代人提交时指定目标用户
|
|
|
|
|
|
class SubmissionUpdate(BaseModel):
|
|
project_phase: Optional[str] = None
|
|
work_type: Optional[str] = None
|
|
content_type: Optional[str] = None
|
|
duration_minutes: Optional[float] = None
|
|
duration_seconds: Optional[float] = None
|
|
hours_spent: Optional[float] = None
|
|
submit_to: Optional[str] = None
|
|
description: Optional[str] = None
|
|
submit_date: Optional[date] = None
|
|
episode_number: Optional[int] = None
|
|
change_reason: str # 修改必须填原因
|
|
|
|
|
|
class SubmissionOut(BaseModel):
|
|
id: int
|
|
user_id: int
|
|
user_name: Optional[str] = None
|
|
project_id: int
|
|
project_name: Optional[str] = None
|
|
project_phase: str
|
|
work_type: str
|
|
content_type: str
|
|
duration_minutes: Optional[float] = 0
|
|
duration_seconds: Optional[float] = 0
|
|
total_seconds: float
|
|
hours_spent: Optional[float] = None
|
|
submit_to: str
|
|
description: Optional[str] = None
|
|
submit_date: date
|
|
milestone_name: Optional[str] = None
|
|
delay_reason: Optional[str] = None
|
|
episode_number: Optional[int] = None
|
|
created_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ──────────────────────────── AI 工具成本 ────────────────────────────
|
|
|
|
class AIToolCostCreate(BaseModel):
|
|
tool_name: str
|
|
subscription_period: str
|
|
amount: float
|
|
allocation_type: str
|
|
project_id: Optional[int] = None
|
|
record_date: date
|
|
allocations: Optional[List[dict]] = None # [{project_id, percentage}]
|
|
|
|
|
|
class AIToolCostOut(BaseModel):
|
|
id: int
|
|
tool_name: str
|
|
subscription_period: str
|
|
amount: float
|
|
allocation_type: str
|
|
project_id: Optional[int] = None
|
|
recorded_by: int
|
|
record_date: date
|
|
created_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ──────────────────────────── 外包成本 ────────────────────────────
|
|
|
|
class OutsourceCostCreate(BaseModel):
|
|
project_id: int
|
|
outsource_type: str
|
|
episode_start: Optional[int] = None
|
|
episode_end: Optional[int] = None
|
|
amount: float
|
|
record_date: date
|
|
|
|
|
|
class OutsourceCostOut(BaseModel):
|
|
id: int
|
|
project_id: int
|
|
outsource_type: str
|
|
episode_start: Optional[int] = None
|
|
episode_end: Optional[int] = None
|
|
amount: float
|
|
recorded_by: int
|
|
record_date: date
|
|
created_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ──────────────────────────── 成本调整 ────────────────────────────
|
|
|
|
class CostOverrideCreate(BaseModel):
|
|
user_id: int
|
|
date: date
|
|
project_id: int
|
|
override_amount: float
|
|
reason: Optional[str] = None
|
|
|
|
|
|
# ──────────────────────────── 固定开支 ────────────────────────────
|
|
|
|
class OverheadCostCreate(BaseModel):
|
|
cost_type: str # 办公室租金/水电费
|
|
amount: float
|
|
record_month: str # YYYY-MM
|
|
note: Optional[str] = None
|
|
|
|
|
|
class OverheadCostOut(BaseModel):
|
|
id: int
|
|
cost_type: str
|
|
amount: float
|
|
record_month: str
|
|
note: Optional[str] = None
|
|
recorded_by: int
|
|
created_at: Optional[datetime] = None
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
# ──────────────────────────── 仪表盘 ────────────────────────────
|
|
|
|
class DashboardSummary(BaseModel):
|
|
"""全局仪表盘数据"""
|
|
active_projects: int = 0
|
|
completed_projects: int = 0
|
|
monthly_labor_cost: float = 0
|
|
monthly_ai_tool_cost: float = 0
|
|
monthly_total_seconds: float = 0
|
|
avg_daily_seconds_per_person: float = 0
|
|
projects: List[dict] = []
|
|
waste_ranking: List[dict] = []
|
|
settled_projects: List[dict] = []
|
|
|
|
|
|
# ──────────────────────────── 项目结算 ────────────────────────────
|
|
|
|
class SettlementOut(BaseModel):
|
|
"""项目结算报告"""
|
|
project_id: int
|
|
project_name: str
|
|
project_type: str
|
|
labor_cost: float = 0
|
|
ai_tool_cost: float = 0
|
|
outsource_cost: float = 0
|
|
total_cost: float = 0
|
|
waste_seconds: float = 0
|
|
waste_rate: float = 0
|
|
test_waste_seconds: float = 0
|
|
overproduction_waste_seconds: float = 0
|
|
contract_amount: Optional[float] = None
|
|
profit_loss: Optional[float] = None
|
|
team_efficiency: List[dict] = []
|