feat: 本月人力成本包含管理层 + 今日提交分组 + 筛选bug修复
- 本月人力成本 = 提交人成本 + 管理层成本(豁免角色) - 今日提交情况分三组:已提交合格(≥8h)、时间不足(<8h)、未提交 - 修复内容提交页同时筛选人和项目时筛选失效的bug Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f9016ab2af
commit
b62da90c8b
@ -34,7 +34,8 @@ def get_dashboard(
|
|||||||
today = date.today()
|
today = date.today()
|
||||||
month_start = today.replace(day=1)
|
month_start = today.replace(day=1)
|
||||||
|
|
||||||
# 本月人力成本(简化:统计本月所有有提交的人的日成本)
|
# 本月人力成本 = 提交人成本 + 管理层成本
|
||||||
|
# 1. 提交人成本(统计本月所有有提交的人的日成本)
|
||||||
monthly_submitters = db.query(Submission.user_id, Submission.submit_date).filter(
|
monthly_submitters = db.query(Submission.user_id, Submission.submit_date).filter(
|
||||||
Submission.submit_date >= month_start,
|
Submission.submit_date >= month_start,
|
||||||
Submission.submit_date <= today,
|
Submission.submit_date <= today,
|
||||||
@ -50,6 +51,26 @@ def get_dashboard(
|
|||||||
if user:
|
if user:
|
||||||
monthly_labor += user.daily_cost
|
monthly_labor += user.daily_cost
|
||||||
|
|
||||||
|
# 2. 管理层成本(豁免提交角色的人)
|
||||||
|
from models import Role
|
||||||
|
exempt_role_ids = set(
|
||||||
|
r.id for r in db.query(Role).filter(Role.exempt_submission == 1).all()
|
||||||
|
)
|
||||||
|
if exempt_role_ids:
|
||||||
|
exempt_users = db.query(User).filter(
|
||||||
|
User.is_active == 1,
|
||||||
|
User.monthly_salary > 0,
|
||||||
|
User.role_id.in_(exempt_role_ids),
|
||||||
|
).all()
|
||||||
|
# 本月有提交记录的工作日数
|
||||||
|
monthly_working_days = db.query(Submission.submit_date).filter(
|
||||||
|
Submission.submit_date >= month_start,
|
||||||
|
Submission.submit_date <= today,
|
||||||
|
).distinct().count()
|
||||||
|
# 管理层成本 = 每人日薪 × 本月工作日数
|
||||||
|
monthly_management = sum(u.daily_cost * monthly_working_days for u in exempt_users)
|
||||||
|
monthly_labor += monthly_management
|
||||||
|
|
||||||
# 本月 AI 工具成本
|
# 本月 AI 工具成本
|
||||||
monthly_ai = db.query(sa_func.sum(AIToolCost.amount)).filter(
|
monthly_ai = db.query(sa_func.sum(AIToolCost.amount)).filter(
|
||||||
AIToolCost.record_date >= month_start,
|
AIToolCost.record_date >= month_start,
|
||||||
@ -237,7 +258,8 @@ def get_dashboard(
|
|||||||
Submission.submit_date == today,
|
Submission.submit_date == today,
|
||||||
).distinct().all()
|
).distinct().all()
|
||||||
)
|
)
|
||||||
submitted_users = []
|
submitted_sufficient = [] # 提交且满8小时
|
||||||
|
submitted_insufficient = [] # 提交但不足8小时
|
||||||
not_submitted_users = []
|
not_submitted_users = []
|
||||||
for u in all_active_users:
|
for u in all_active_users:
|
||||||
info = {"id": u.id, "name": u.name}
|
info = {"id": u.id, "name": u.name}
|
||||||
@ -247,14 +269,19 @@ def get_dashboard(
|
|||||||
Submission.submit_date == today,
|
Submission.submit_date == today,
|
||||||
).scalar() or 0
|
).scalar() or 0
|
||||||
info["hours"] = round(hours, 1)
|
info["hours"] = round(hours, 1)
|
||||||
submitted_users.append(info)
|
if hours >= 8:
|
||||||
|
submitted_sufficient.append(info)
|
||||||
|
else:
|
||||||
|
submitted_insufficient.append(info)
|
||||||
else:
|
else:
|
||||||
not_submitted_users.append(info)
|
not_submitted_users.append(info)
|
||||||
daily_attendance = {
|
daily_attendance = {
|
||||||
"total": len(all_active_users),
|
"total": len(all_active_users),
|
||||||
"submitted_count": len(submitted_users),
|
"submitted_sufficient_count": len(submitted_sufficient),
|
||||||
|
"submitted_insufficient_count": len(submitted_insufficient),
|
||||||
"not_submitted_count": len(not_submitted_users),
|
"not_submitted_count": len(not_submitted_users),
|
||||||
"submitted": submitted_users,
|
"submitted_sufficient": submitted_sufficient,
|
||||||
|
"submitted_insufficient": submitted_insufficient,
|
||||||
"not_submitted": not_submitted_users,
|
"not_submitted": not_submitted_users,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -48,14 +48,13 @@ def list_submissions(
|
|||||||
current_user: User = Depends(get_current_user)
|
current_user: User = Depends(get_current_user)
|
||||||
):
|
):
|
||||||
q = db.query(Submission)
|
q = db.query(Submission)
|
||||||
# 查看项目内提交时,所有人都可见(方便横向对比)
|
# 权限检查:没有 user:view 权限只能看自己的
|
||||||
# 全局提交列表时,没有 user:view 权限只能看自己的
|
if not current_user.has_permission("user:view"):
|
||||||
if project_id:
|
|
||||||
pass # 项目内提交不做用户过滤
|
|
||||||
elif not current_user.has_permission("user:view"):
|
|
||||||
q = q.filter(Submission.user_id == current_user.id)
|
q = q.filter(Submission.user_id == current_user.id)
|
||||||
elif user_id:
|
elif user_id:
|
||||||
|
# 有权限且指定了用户,过滤该用户
|
||||||
q = q.filter(Submission.user_id == user_id)
|
q = q.filter(Submission.user_id == user_id)
|
||||||
|
# 项目筛选
|
||||||
if project_id:
|
if project_id:
|
||||||
q = q.filter(Submission.project_id == project_id)
|
q = q.filter(Submission.project_id == project_id)
|
||||||
if start_date:
|
if start_date:
|
||||||
|
|||||||
@ -74,22 +74,29 @@
|
|||||||
今日提交情况
|
今日提交情况
|
||||||
</span>
|
</span>
|
||||||
<div class="attendance-summary">
|
<div class="attendance-summary">
|
||||||
<span class="att-tag submitted">已提交 {{ data.daily_attendance.submitted_count }}</span>
|
<span class="att-tag submitted">已提交合格 {{ data.daily_attendance.submitted_sufficient_count }}</span>
|
||||||
|
<span class="att-tag insufficient" v-if="data.daily_attendance.submitted_insufficient_count > 0">时间不足 {{ data.daily_attendance.submitted_insufficient_count }}</span>
|
||||||
<span class="att-tag not-submitted" v-if="data.daily_attendance.not_submitted_count > 0">未提交 {{ data.daily_attendance.not_submitted_count }}</span>
|
<span class="att-tag not-submitted" v-if="data.daily_attendance.not_submitted_count > 0">未提交 {{ data.daily_attendance.not_submitted_count }}</span>
|
||||||
<el-icon :size="14" style="color:var(--text-secondary);margin-left:4px;transition:transform 0.2s" :style="{transform: attendanceExpanded ? 'rotate(180deg)' : ''}"><ArrowDown /></el-icon>
|
<el-icon :size="14" style="color:var(--text-secondary);margin-left:4px;transition:transform 0.2s" :style="{transform: attendanceExpanded ? 'rotate(180deg)' : ''}"><ArrowDown /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body attendance-body" v-show="attendanceExpanded">
|
<div class="card-body attendance-body" v-show="attendanceExpanded">
|
||||||
<div v-if="data.daily_attendance.not_submitted?.length" class="att-section">
|
<div v-if="data.daily_attendance.not_submitted?.length" class="att-section">
|
||||||
<div class="att-section-title not-submitted-title">未提交({{ data.daily_attendance.not_submitted.length }}人)</div>
|
<div class="att-section-title not-submitted-title">❌ 未提交({{ data.daily_attendance.not_submitted.length }}人)</div>
|
||||||
<div class="att-user-list">
|
<div class="att-user-list">
|
||||||
<span v-for="u in data.daily_attendance.not_submitted" :key="u.id" class="att-user not-submitted">{{ u.name }}</span>
|
<span v-for="u in data.daily_attendance.not_submitted" :key="u.id" class="att-user not-submitted">{{ u.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="data.daily_attendance.submitted?.length" class="att-section">
|
<div v-if="data.daily_attendance.submitted_insufficient?.length" class="att-section">
|
||||||
<div class="att-section-title">已提交({{ data.daily_attendance.submitted.length }}人)</div>
|
<div class="att-section-title insufficient-title">⚠️ 时间不足({{ data.daily_attendance.submitted_insufficient.length }}人)</div>
|
||||||
<div class="att-user-list">
|
<div class="att-user-list">
|
||||||
<span v-for="u in data.daily_attendance.submitted" :key="u.id" class="att-user submitted">{{ u.name }} <small>{{ u.hours }}h</small></span>
|
<span v-for="u in data.daily_attendance.submitted_insufficient" :key="u.id" class="att-user insufficient">{{ u.name }} <small>{{ u.hours }}h</small></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="data.daily_attendance.submitted_sufficient?.length" class="att-section">
|
||||||
|
<div class="att-section-title">✅ 已提交合格({{ data.daily_attendance.submitted_sufficient.length }}人)</div>
|
||||||
|
<div class="att-user-list">
|
||||||
|
<span v-for="u in data.daily_attendance.submitted_sufficient" :key="u.id" class="att-user submitted">{{ u.name }} <small>{{ u.hours }}h</small></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -491,17 +498,20 @@ onUnmounted(() => {
|
|||||||
.attendance-summary { display: flex; align-items: center; gap: 8px; }
|
.attendance-summary { display: flex; align-items: center; gap: 8px; }
|
||||||
.att-tag { font-size: 13px; font-weight: 600; padding: 2px 10px; border-radius: 10px; }
|
.att-tag { font-size: 13px; font-weight: 600; padding: 2px 10px; border-radius: 10px; }
|
||||||
.att-tag.submitted { color: #34C759; background: #E8F8EE; }
|
.att-tag.submitted { color: #34C759; background: #E8F8EE; }
|
||||||
|
.att-tag.insufficient { color: #FF9500; background: #FFF3E0; }
|
||||||
.att-tag.not-submitted { color: #FF3B30; background: #FFE8E7; }
|
.att-tag.not-submitted { color: #FF3B30; background: #FFE8E7; }
|
||||||
.attendance-body { padding-top: 8px !important; padding-bottom: 12px !important; }
|
.attendance-body { padding-top: 8px !important; padding-bottom: 12px !important; }
|
||||||
.att-section { margin-bottom: 12px; }
|
.att-section { margin-bottom: 12px; }
|
||||||
.att-section:last-child { margin-bottom: 0; }
|
.att-section:last-child { margin-bottom: 0; }
|
||||||
.att-section-title { font-size: 12px; color: var(--text-secondary); margin-bottom: 8px; font-weight: 500; }
|
.att-section-title { font-size: 12px; color: var(--text-secondary); margin-bottom: 8px; font-weight: 500; }
|
||||||
.att-section-title.not-submitted-title { color: #FF3B30; }
|
.att-section-title.not-submitted-title { color: #FF3B30; }
|
||||||
|
.att-section-title.insufficient-title { color: #FF9500; }
|
||||||
.att-user-list { display: flex; flex-wrap: wrap; gap: 6px; }
|
.att-user-list { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||||
.att-user {
|
.att-user {
|
||||||
font-size: 13px; padding: 4px 12px; border-radius: 6px; white-space: nowrap;
|
font-size: 13px; padding: 4px 12px; border-radius: 6px; white-space: nowrap;
|
||||||
}
|
}
|
||||||
.att-user.not-submitted { background: #FFF5F5; color: #CC2D25; font-weight: 500; }
|
.att-user.not-submitted { background: #FFF5F5; color: #CC2D25; font-weight: 500; }
|
||||||
|
.att-user.insufficient { background: #FFF3E0; color: #D97706; font-weight: 500; }
|
||||||
.att-user.submitted { background: #F7F8FA; color: var(--text-regular); }
|
.att-user.submitted { background: #F7F8FA; color: var(--text-regular); }
|
||||||
.att-user.submitted small { color: var(--text-secondary); margin-left: 2px; }
|
.att-user.submitted small { color: var(--text-secondary); margin-left: 2px; }
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user