seaislee1209 9970446ece feat(浠〃鐩?: 鏂板4涓狤Charts鍙鍖栧浘琛?
- 杩?0澶╀骇鍑鸿秼鍔挎姌绾垮浘
- 鎴愭湰鏋勬垚鐜舰楗煎浘锛堜汉鍔?AI宸ュ叿/澶栧寘锛?- 椤圭洰浜у嚭瀵规瘮妯悜鏌辩姸鍥?- 鎹熻€楁帓琛屾煴鐘跺浘锛堟寜鎹熻€楃巼鐫€鑹诧級
- 鍚庣鏂板 daily_trend / cost_breakdown / project_comparison 鏁版嵁鎺ュ彛

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 15:29:20 +08:00

198 lines
7.1 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 fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from sqlalchemy import func as sa_func
from datetime import date, timedelta
from database import get_db
from models import (
User, UserRole, Project, Submission, AIToolCost,
ProjectStatus, ProjectType, WorkType
)
from auth import get_current_user, require_role
from calculations import (
calc_project_settlement, calc_waste_for_project,
calc_labor_cost_for_project, calc_ai_tool_cost_for_project,
calc_outsource_cost_for_project, calc_team_efficiency
)
router = APIRouter(prefix="/api", tags=["仪表盘与结算"])
@router.get("/dashboard")
def get_dashboard(
db: Session = Depends(get_db),
current_user: User = Depends(require_role(UserRole.OWNER))
):
"""全局仪表盘数据"""
# 项目概览
active = db.query(Project).filter(Project.status == ProjectStatus.IN_PROGRESS).all()
completed = db.query(Project).filter(Project.status == ProjectStatus.COMPLETED).all()
# 当月日期范围
today = date.today()
month_start = today.replace(day=1)
# 本月人力成本(简化:统计本月所有有提交的人的日成本)
monthly_submitters = db.query(Submission.user_id, Submission.submit_date).filter(
Submission.submit_date >= month_start,
Submission.submit_date <= today,
).distinct().all()
monthly_labor = 0.0
processed_user_dates = set()
for uid, d in monthly_submitters:
key = (uid, d)
if key not in processed_user_dates:
processed_user_dates.add(key)
user = db.query(User).filter(User.id == uid).first()
if user:
monthly_labor += user.daily_cost
# 本月 AI 工具成本
monthly_ai = db.query(sa_func.sum(AIToolCost.amount)).filter(
AIToolCost.record_date >= month_start,
AIToolCost.record_date <= today,
).scalar() or 0
# 本月总产出秒数
monthly_secs = db.query(sa_func.sum(Submission.total_seconds)).filter(
Submission.submit_date >= month_start,
Submission.submit_date <= today,
Submission.total_seconds > 0,
).scalar() or 0
# 活跃人数
active_users = db.query(Submission.user_id).filter(
Submission.submit_date >= month_start,
).distinct().count()
working_days = max(1, (today - month_start).days + 1)
avg_daily = round(monthly_secs / max(1, active_users) / working_days, 1)
# 各项目摘要
project_summaries = []
for p in active:
waste = calc_waste_for_project(p.id, db)
total_secs = waste.get("total_submitted_seconds", 0)
target = p.target_total_seconds
progress = round(total_secs / target * 100, 1) if target > 0 else 0
is_overdue = (
p.estimated_completion_date and today > p.estimated_completion_date
)
project_summaries.append({
"id": p.id,
"name": p.name,
"project_type": p.project_type.value if hasattr(p.project_type, 'value') else p.project_type,
"progress_percent": progress,
"target_seconds": target,
"submitted_seconds": total_secs,
"waste_rate": waste.get("waste_rate", 0),
"is_overdue": bool(is_overdue),
"estimated_completion_date": str(p.estimated_completion_date) if p.estimated_completion_date else None,
})
# 损耗排行
waste_ranking = []
for p in active + completed:
w = calc_waste_for_project(p.id, db)
if w.get("total_waste_seconds", 0) > 0:
waste_ranking.append({
"project_id": p.id,
"project_name": p.name,
"waste_seconds": w["total_waste_seconds"],
"waste_rate": w["waste_rate"],
})
waste_ranking.sort(key=lambda x: x["waste_rate"], reverse=True)
# 已结算项目
settled = []
for p in completed:
settlement = calc_project_settlement(p.id, db)
settled.append({
"project_id": p.id,
"project_name": p.name,
"project_type": settlement.get("project_type", ""),
"total_cost": settlement.get("total_cost", 0),
"contract_amount": settlement.get("contract_amount"),
"profit_loss": settlement.get("profit_loss"),
})
# ── 图表数据近30天每日产出趋势 ──
daily_trend = []
for i in range(29, -1, -1):
d = today - timedelta(days=i)
day_secs = db.query(sa_func.sum(Submission.total_seconds)).filter(
Submission.submit_date == d,
Submission.total_seconds > 0,
).scalar() or 0
daily_trend.append({
"date": str(d),
"seconds": round(day_secs, 1),
})
# ── 图表数据:成本构成 ──
total_labor_all = 0.0
total_ai_all = 0.0
total_outsource_all = 0.0
for p in active + completed:
total_labor_all += calc_labor_cost_for_project(p.id, db)
total_ai_all += calc_ai_tool_cost_for_project(p.id, db)
total_outsource_all += calc_outsource_cost_for_project(p.id, db)
cost_breakdown = [
{"name": "人力成本", "value": round(total_labor_all, 0)},
{"name": "AI工具", "value": round(total_ai_all, 0)},
{"name": "外包", "value": round(total_outsource_all, 0)},
]
# ── 图表数据:各项目产出对比(进行中项目) ──
project_comparison = []
for p in active:
total_secs_p = db.query(sa_func.sum(Submission.total_seconds)).filter(
Submission.project_id == p.id,
Submission.total_seconds > 0,
).scalar() or 0
project_comparison.append({
"name": p.name,
"submitted": round(total_secs_p, 0),
"target": p.target_total_seconds,
})
return {
"active_projects": len(active),
"completed_projects": len(completed),
"monthly_labor_cost": round(monthly_labor, 2),
"monthly_ai_tool_cost": round(monthly_ai, 2),
"monthly_total_seconds": round(monthly_secs, 1),
"avg_daily_seconds_per_person": avg_daily,
"projects": project_summaries,
"waste_ranking": waste_ranking,
"settled_projects": settled,
# 图表数据
"daily_trend": daily_trend,
"cost_breakdown": cost_breakdown,
"project_comparison": project_comparison,
}
@router.get("/projects/{project_id}/settlement")
def get_settlement(
project_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(require_role(UserRole.OWNER))
):
"""项目结算报告"""
project = db.query(Project).filter(Project.id == project_id).first()
if not project:
raise HTTPException(status_code=404, detail="项目不存在")
return calc_project_settlement(project_id, db)
@router.get("/projects/{project_id}/efficiency")
def get_efficiency(
project_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(require_role(UserRole.OWNER, UserRole.SUPERVISOR, UserRole.LEADER))
):
"""项目团队效率数据"""
return calc_team_efficiency(project_id, db)