From a5d3739eef29dbb0090931988c093443fe60a557 Mon Sep 17 00:00:00 2001 From: seaislee1209 Date: Tue, 10 Mar 2026 11:11:15 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=86=85=E9=83=A8=E4=BA=8B=E5=8A=A1?= =?UTF-8?q?=E6=88=90=E6=9C=AC=E6=8C=89=E6=AF=94=E4=BE=8B=E5=88=86=E6=91=8A?= =?UTF-8?q?=E5=88=B0=E6=89=80=E6=9C=89=E9=A1=B9=E7=9B=AE=20+=20UI=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 内部事务成本池:非管理层用户的 时薪×投入时长,按产出秒数比例分摊 - 管理层用户提交内部事务不重复计算(已通过管理成本分摊) - 人力成本排除内部事务提交,避免重复 - 项目管理页面隐藏"内部事务"项目 - 阶段列宽度调整适配"内部事务"四字显示 Co-Authored-By: Claude Opus 4.6 --- backend/calculations.py | 72 +++++++++++++++++++++++++++++- frontend/src/views/Projects.vue | 5 ++- frontend/src/views/Submissions.vue | 2 +- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/backend/calculations.py b/backend/calculations.py index ffd015c..d25b5f8 100644 --- a/backend/calculations.py +++ b/backend/calculations.py @@ -54,10 +54,11 @@ def calc_labor_cost_for_project(project_id: int, db: Session) -> float: total_labor += override.override_amount continue - # 这个人这天所有项目的提交 + # 这个人这天所有项目的提交(排除内部事务,其成本单独分摊) day_subs = db.query(Submission).filter( Submission.user_id == uid, Submission.submit_date == d, + Submission.project_phase != PhaseGroup.INTERNAL, ).all() # 计算这天各项目的秒数和条数 @@ -217,6 +218,71 @@ def calc_management_cost_for_project(project_id: int, db: Session) -> float: return 0.0 +# ──────────────────────────── 内部事务成本分摊 ──────────────────────────── + +def calc_internal_affairs_cost_for_project(project_id: int, db: Session) -> float: + """ + 计算某项目分摊的内部事务成本(培训/招聘面试等) + 规则: + - 汇总"内部事务"项目下非管理层用户的 hours_spent × 时薪 + - 管理层用户(exempt_submission=1)的成本已通过管理成本分摊,不重复计算 + - 总池按各项目产出秒数比例分摊到所有进行中项目 + """ + from models import Project, ProjectStatus, Role + + # 找到内部事务项目 + internal_proj = db.query(Project).filter(Project.name == "内部事务").first() + if not internal_proj: + return 0.0 + + # 内部事务项目不分摊给自己 + if project_id == internal_proj.id: + return 0.0 + + # 管理层用户 ID(exempt_submission=1),其成本已在管理成本中计算 + exempt_role_ids = set( + r.id for r in db.query(Role).filter(Role.exempt_submission == 1).all() + ) + + # 汇总内部事务的总成本:非管理层用户的 hours_spent × 时薪 + internal_subs = db.query(Submission).filter( + Submission.project_id == internal_proj.id, + ).all() + if not internal_subs: + return 0.0 + + total_pool = 0.0 + user_cache = {} + for s in internal_subs: + if s.user_id not in user_cache: + user_cache[s.user_id] = db.query(User).filter(User.id == s.user_id).first() + user = user_cache[s.user_id] + if not user or user.daily_cost <= 0: + continue + # 跳过管理层用户(已通过管理成本分摊) + if user.role_id in exempt_role_ids: + continue + hourly_cost = user.daily_cost / 8 + total_pool += (s.hours_spent or 0) * hourly_cost + + if total_pool <= 0: + return 0.0 + + # 按产出秒数比例分摊(排除内部事务项目本身) + all_secs = db.query(sa_func.sum(Submission.total_seconds)).filter( + Submission.total_seconds > 0, + Submission.project_id != internal_proj.id, + ).scalar() or 0 + proj_secs = db.query(sa_func.sum(Submission.total_seconds)).filter( + Submission.project_id == project_id, + Submission.total_seconds > 0, + ).scalar() or 0 + + if all_secs > 0: + return round(total_pool * proj_secs / all_secs, 2) + return 0.0 + + # ──────────────────────────── 工作日计算工具 ──────────────────────────── def _working_days_between(start_date, end_date) -> int: @@ -497,7 +563,8 @@ def calc_project_settlement(project_id: int, db: Session) -> dict: outsource = calc_outsource_cost_for_project(project_id, db) overhead = calc_overhead_cost_for_project(project_id, db) management = calc_management_cost_for_project(project_id, db) - total_cost = labor + ai_tool + outsource + overhead + management + internal_affairs = calc_internal_affairs_cost_for_project(project_id, db) + total_cost = labor + ai_tool + outsource + overhead + management + internal_affairs waste = calc_waste_for_project(project_id, db) efficiency = calc_team_efficiency(project_id, db) @@ -510,6 +577,7 @@ def calc_project_settlement(project_id: int, db: Session) -> dict: "outsource_cost": outsource, "overhead_cost": overhead, "management_cost": management, + "internal_affairs_cost": internal_affairs, "total_cost": round(total_cost, 2), **waste, "team_efficiency": efficiency, diff --git a/frontend/src/views/Projects.vue b/frontend/src/views/Projects.vue index 3a5bc5e..47a9f3f 100644 --- a/frontend/src/views/Projects.vue +++ b/frontend/src/views/Projects.vue @@ -191,7 +191,10 @@ function stageLabel(row) { async function load() { loading.value = true - try { projects.value = await projectApi.list(filter) } finally { loading.value = false } + try { + const all = await projectApi.list(filter) + projects.value = all.filter(p => p.name !== '内部事务') + } finally { loading.value = false } } async function handleCreate() { diff --git a/frontend/src/views/Submissions.vue b/frontend/src/views/Submissions.vue index 2a74440..8569e31 100644 --- a/frontend/src/views/Submissions.vue +++ b/frontend/src/views/Submissions.vue @@ -36,7 +36,7 @@ - +