airlabs-manage/backend/schemas.py
seaislee1209 41c2b9cd89
All checks were successful
Build and Deploy Backend / build-and-deploy (push) Successful in 2m5s
Build and Deploy Web / build-and-deploy (push) Successful in 1m32s
feat: 批量多集提交 — 集数多选+投入时长自动均分
- 后端 create_submission 支持 episode_numbers 批量创建,hours_spent 按集数均分
- 前端集数选择器改为多选(collapse-tags),显示分配提示
- 后端 API 端口统一改为 8001(.env.development + vite.config.js)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 19:21:11 +08:00

330 lines
9.7 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
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] = []