diff --git a/backend/calculations.py b/backend/calculations.py index 0ed9f35..b955524 100644 --- a/backend/calculations.py +++ b/backend/calculations.py @@ -346,11 +346,13 @@ def calc_waste_for_project(project_id: int, db: Session) -> dict: def calc_team_efficiency(project_id: int, db: Session) -> list: """ - 时均产出效率法(展示日均): - - 效率对比用时均产出 = 累计秒数 ÷ 累计工时(跨项目公平) - - 前端展示日均产出 = 累计秒数 ÷ 提交天数(直观) + 加权效率算法: + - 加权效率 = (制作秒数 - 修改秒数) / 总工时 (综合速度+质量) + - 通过率 = (制作秒数 - 修改秒数) / 制作秒数 (纯质量指标) + - 日均净产出 = (制作秒数 - 修改秒数) / 活跃天数 + - 熟练度等级基于加权效率与团队均值的比值 """ - from sqlalchemy import distinct + from sqlalchemy import distinct, case per_user = db.query( Submission.user_id, @@ -358,6 +360,14 @@ def calc_team_efficiency(project_id: int, db: Session) -> list: sa_func.sum(Submission.hours_spent).label("total_hours"), sa_func.count(distinct(Submission.submit_date)).label("days"), sa_func.count(Submission.id).label("count"), + sa_func.sum(case( + (Submission.work_type == WorkType.PRODUCTION, Submission.total_seconds), + else_=0, + )).label("production_secs"), + sa_func.sum(case( + (Submission.work_type == WorkType.REVISION, Submission.total_seconds), + else_=0, + )).label("revision_secs"), ).filter( Submission.project_id == project_id, Submission.total_seconds > 0, @@ -367,10 +377,21 @@ def calc_team_efficiency(project_id: int, db: Session) -> list: return [] user_data = [] - for user_id, total_secs, total_hours, days, count in per_user: + for user_id, total_secs, total_hours, days, count, prod_secs, rev_secs in per_user: user = db.query(User).filter(User.id == user_id).first() + prod_secs = prod_secs or 0 + rev_secs = rev_secs or 0 + net_secs = max(prod_secs - rev_secs, 0) + + # 原有指标(向后兼容,结算页面依赖) daily_avg = total_secs / days if days > 0 else 0 hourly_output = total_secs / total_hours if total_hours and total_hours > 0 else 0 + + # 新指标 + first_pass_rate = round(net_secs / prod_secs * 100, 1) if prod_secs > 0 else 0 + weighted_efficiency = net_secs / total_hours if total_hours and total_hours > 0 else 0 + daily_net_output = net_secs / days if days > 0 else 0 + user_data.append({ "user_id": user_id, "user_name": user.name if user else "未知", @@ -380,17 +401,35 @@ def calc_team_efficiency(project_id: int, db: Session) -> list: "active_days": days, "daily_avg": round(daily_avg, 1), "hourly_output": round(hourly_output, 1), + # 新字段 + "production_seconds": round(prod_secs, 1), + "revision_seconds": round(rev_secs, 1), + "first_pass_rate": first_pass_rate, + "weighted_efficiency": round(weighted_efficiency, 1), + "daily_net_output": round(daily_net_output, 1), }) - # 效率对比用时均产出(公平) - team_hourly_avg = sum(d["hourly_output"] for d in user_data) / len(user_data) + # 熟练度等级:基于加权效率与团队均值的比值 + team_weighted_avg = sum(d["weighted_efficiency"] for d in user_data) / len(user_data) for d in user_data: - diff = d["hourly_output"] - team_hourly_avg - d["team_hourly_avg"] = round(team_hourly_avg, 1) - d["efficiency_rate"] = round(diff / team_hourly_avg * 100, 1) if team_hourly_avg > 0 else 0 + d["team_weighted_avg"] = round(team_weighted_avg, 1) + ratio = d["weighted_efficiency"] / team_weighted_avg if team_weighted_avg > 0 else 0 + # 效率百分比(与团队均值的偏差) + d["efficiency_rate"] = round((ratio - 1) * 100, 1) if team_weighted_avg > 0 else 0 + # 熟练度等级 + if ratio >= 1.5: + d["proficiency_grade"] = "S+" + elif ratio >= 1.2: + d["proficiency_grade"] = "S" + elif ratio >= 0.8: + d["proficiency_grade"] = "A" + elif ratio >= 0.5: + d["proficiency_grade"] = "B" + else: + d["proficiency_grade"] = "C" - user_data.sort(key=lambda x: x["hourly_output"], reverse=True) + user_data.sort(key=lambda x: x["weighted_efficiency"], reverse=True) return user_data diff --git a/backend/main.py b/backend/main.py index 18815ad..7975efe 100644 --- a/backend/main.py +++ b/backend/main.py @@ -186,14 +186,20 @@ def init_roles_and_admin(): db.add(role) print(f"[OK] created role: {role_name}") elif existing.is_system: - # 同步内置角色:补充代码中新增的权限 + # 同步内置角色:完全对齐代码定义(增+减) current = set(existing.permissions or []) defined = set(role_def["permissions"]) - missing = defined - current - if missing: - existing.permissions = list(current | missing) + added = defined - current + removed = current - defined + if added or removed: + existing.permissions = list(defined) flag_modified(existing, "permissions") - print(f"[SYNC] added permissions to {role_name}: {missing}") + parts = [] + if added: + parts.append(f"added {added}") + if removed: + parts.append(f"removed {removed}") + print(f"[SYNC] {role_name}: {', '.join(parts)}") db.commit() # 迁移旧成本权限 → 细分权限 diff --git a/backend/models.py b/backend/models.py index b2a8801..4c0f2ad 100644 --- a/backend/models.py +++ b/backend/models.py @@ -28,6 +28,8 @@ ALL_PERMISSIONS = [ # 内容提交 ("submission:view", "查看提交记录", "内容提交"), ("submission:create", "新增提交", "内容提交"), + ("submission:proxy", "代人提交", "内容提交"), + ("submission:delete", "删除提交记录", "内容提交"), # 成本管理 —— 按类型细分 ("cost_ai:view", "查看AI工具成本", "成本管理"), ("cost_ai:create", "录入AI工具成本", "成本管理"), @@ -69,23 +71,21 @@ BUILTIN_ROLES = { "permissions": PERMISSION_KEYS[:], # 全部 }, "主管": { - "description": "管理项目和成本,不可管理用户和角色", + "description": "管理项目和提交,可查看AI工具与外包成本", "permissions": [ - "dashboard:view", "dashboard:view_cost", "dashboard:view_waste", "dashboard:view_profit", "dashboard:view_risk", + "dashboard:view", "dashboard:view_waste", "dashboard:view_risk", "project:view", "project:create", "project:edit", "project:complete", - "submission:view", "submission:create", + "submission:view", "submission:create", "submission:proxy", "submission:delete", "cost_ai:view", "cost_ai:create", "cost_ai:delete", "cost_outsource:view", "cost_outsource:create", "cost_outsource:delete", - "cost_overhead:view", "cost_overhead:create", "cost_overhead:delete", - "cost_labor:view", "cost_labor:create", "user:view", - "settlement:view", "efficiency:view", + "efficiency:view", ], }, "组长": { "description": "管理本组提交和查看成本", "permissions": [ - "project:view", "project:create", + "project:view", "submission:view", "submission:create", "cost_ai:view", "cost_ai:create", "efficiency:view", diff --git a/backend/routers/submissions.py b/backend/routers/submissions.py index 5a24387..6068b69 100644 --- a/backend/routers/submissions.py +++ b/backend/routers/submissions.py @@ -72,6 +72,16 @@ def create_submission( db: Session = Depends(get_db), current_user: User = Depends(get_current_user) ): + # 代人提交:有 submission:proxy 权限时可以指定 user_id + target_user_id = current_user.id + if req.user_id and req.user_id != current_user.id: + if not current_user.has_permission("submission:proxy"): + raise HTTPException(status_code=403, detail="没有代人提交权限") + target_user = db.query(User).filter(User.id == req.user_id).first() + if not target_user: + raise HTTPException(status_code=404, detail="目标用户不存在") + target_user_id = req.user_id + # 校验项目存在 project = db.query(Project).filter(Project.id == req.project_id).first() if not project: @@ -100,7 +110,7 @@ def create_submission( ) sub = Submission( - user_id=current_user.id, + user_id=target_user_id, project_id=req.project_id, project_phase=PhaseGroup(req.project_phase), work_type=WorkType(req.work_type), @@ -222,3 +232,22 @@ def get_submission_history( } for r in records ] + + +@router.delete("/{submission_id}") +def delete_submission( + submission_id: int, + db: Session = Depends(get_db), + current_user: User = Depends(require_permission("submission:delete")) +): + """删除提交记录(需要 submission:delete 权限)""" + sub = db.query(Submission).filter(Submission.id == submission_id).first() + if not sub: + raise HTTPException(status_code=404, detail="提交记录不存在") + # 同时删除关联的修改历史 + db.query(SubmissionHistory).filter( + SubmissionHistory.submission_id == submission_id + ).delete() + db.delete(sub) + db.commit() + return {"detail": "删除成功"} diff --git a/backend/schemas.py b/backend/schemas.py index 67105f4..edd3389 100644 --- a/backend/schemas.py +++ b/backend/schemas.py @@ -166,6 +166,7 @@ class SubmissionCreate(BaseModel): submit_date: date delay_reason: Optional[str] = None episode_number: Optional[int] = None + user_id: Optional[int] = None # 代人提交时指定目标用户 class SubmissionUpdate(BaseModel): diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 5bd2b7c..0f6e8b8 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -77,6 +77,7 @@ export const submissionApi = { list: (params) => api.get('/submissions', { params }), create: (data) => api.post('/submissions', data), update: (id, data) => api.put(`/submissions/${id}`, data), + delete: (id) => api.delete(`/submissions/${id}`), history: (id) => api.get(`/submissions/${id}/history`), } diff --git a/frontend/src/views/ProjectDetail.vue b/frontend/src/views/ProjectDetail.vue index 2582a29..36f0fc8 100644 --- a/frontend/src/views/ProjectDetail.vue +++ b/frontend/src/views/ProjectDetail.vue @@ -261,17 +261,40 @@