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 @@
-
+
{{ row.work_type }}