seaislee1209 4629525d2a
All checks were successful
Build and Deploy Backend / build-and-deploy (push) Successful in 1m24s
Build and Deploy Web / build-and-deploy (push) Successful in 54s
feat: 编辑提交记录支持修改所属项目
- 编辑弹窗中所属项目改为可选下拉
- 后端 SubmissionUpdate 新增 project_id 字段
- 切换到内部事务项目时自动调整阶段和内容类型
- 修复编辑时产出时长校验变量名 bug

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 11:17:34 +08:00

334 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_id: Optional[int] = None
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] = []