Files
hospital_performance/backend/app/services/performance_plan_service.py
2026-02-28 15:06:52 +08:00

342 lines
10 KiB
Python

"""
绩效计划服务层
"""
import json
from typing import Optional, List, Dict, Any
from datetime import datetime
from sqlalchemy import select, func, and_
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from app.models.models import PerformancePlan, PlanKpiRelation, PlanStatus, PlanLevel, Indicator
from app.schemas.schemas import PerformancePlanCreate, PerformancePlanUpdate, PlanKpiRelationCreate
class PerformancePlanService:
"""绩效计划服务"""
@staticmethod
async def get_list(
db: AsyncSession,
plan_level: Optional[str] = None,
plan_year: Optional[int] = None,
department_id: Optional[int] = None,
status: Optional[str] = None,
page: int = 1,
page_size: int = 20
) -> tuple[List[PerformancePlan], int]:
"""获取绩效计划列表"""
query = select(PerformancePlan).options(
selectinload(PerformancePlan.department),
selectinload(PerformancePlan.staff)
)
# 构建查询条件
conditions = []
if plan_level:
conditions.append(PerformancePlan.plan_level == plan_level)
if plan_year:
conditions.append(PerformancePlan.plan_year == plan_year)
if department_id:
conditions.append(PerformancePlan.department_id == department_id)
if status:
conditions.append(PerformancePlan.status == status)
if conditions:
query = query.where(and_(*conditions))
# 统计总数
count_query = select(func.count()).select_from(query.subquery())
total = await db.scalar(count_query)
# 分页
query = query.order_by(PerformancePlan.created_at.desc())
query = query.offset((page - 1) * page_size).limit(page_size)
result = await db.execute(query)
plans = result.scalars().all()
return plans, total or 0
@staticmethod
async def get_by_id(db: AsyncSession, plan_id: int) -> Optional[PerformancePlan]:
"""根据 ID 获取绩效计划详情"""
result = await db.execute(
select(PerformancePlan)
.options(
selectinload(PerformancePlan.department),
selectinload(PerformancePlan.staff),
selectinload(PerformancePlan.kpi_relations).selectinload(PlanKpiRelation.indicator)
)
.where(PerformancePlan.id == plan_id)
)
return result.scalar_one_or_none()
@staticmethod
async def create(
db: AsyncSession,
plan_data: PerformancePlanCreate,
submitter_id: Optional[int] = None
) -> PerformancePlan:
"""创建绩效计划"""
# 创建计划
plan_dict = plan_data.model_dump(exclude={'kpi_relations'})
plan = PerformancePlan(**plan_dict)
plan.submitter_id = submitter_id
db.add(plan)
await db.flush()
await db.refresh(plan)
# 创建指标关联
if plan_data.kpi_relations:
for kpi_data in plan_data.kpi_relations:
kpi_relation = PlanKpiRelation(
plan_id=plan.id,
**kpi_data.model_dump()
)
db.add(kpi_relation)
await db.commit()
await db.refresh(plan)
return plan
@staticmethod
async def update(
db: AsyncSession,
plan_id: int,
plan_data: PerformancePlanUpdate
) -> Optional[PerformancePlan]:
"""更新绩效计划"""
plan = await PerformancePlanService.get_by_id(db, plan_id)
if not plan:
return None
update_data = plan_data.model_dump(exclude_unset=True)
for key, value in update_data.items():
if hasattr(plan, key):
setattr(plan, key, value)
# 处理审批相关
if plan_data.status == PlanStatus.APPROVED:
plan.approve_time = datetime.utcnow()
elif plan_data.status == PlanStatus.REJECTED:
plan.approve_time = datetime.utcnow()
await db.commit()
await db.refresh(plan)
return plan
@staticmethod
async def submit(db: AsyncSession, plan_id: int) -> Optional[PerformancePlan]:
"""提交绩效计划"""
plan = await PerformancePlanService.get_by_id(db, plan_id)
if not plan or plan.status != PlanStatus.DRAFT:
return None
plan.status = PlanStatus.PENDING
plan.submit_time = datetime.utcnow()
await db.commit()
await db.refresh(plan)
return plan
@staticmethod
async def approve(
db: AsyncSession,
plan_id: int,
approver_id: int,
approved: bool,
remark: Optional[str] = None
) -> Optional[PerformancePlan]:
"""审批绩效计划"""
plan = await PerformancePlanService.get_by_id(db, plan_id)
if not plan or plan.status != PlanStatus.PENDING:
return None
plan.approver_id = approver_id
plan.approve_time = datetime.utcnow()
plan.approve_remark = remark
if approved:
plan.status = PlanStatus.APPROVED
else:
plan.status = PlanStatus.REJECTED
await db.commit()
await db.refresh(plan)
return plan
@staticmethod
async def activate(db: AsyncSession, plan_id: int) -> Optional[PerformancePlan]:
"""激活绩效计划"""
plan = await PerformancePlanService.get_by_id(db, plan_id)
if not plan or plan.status != PlanStatus.APPROVED:
return None
plan.status = PlanStatus.ACTIVE
plan.is_active = True
await db.commit()
await db.refresh(plan)
return plan
@staticmethod
async def delete(db: AsyncSession, plan_id: int) -> bool:
"""删除绩效计划"""
plan = await PerformancePlanService.get_by_id(db, plan_id)
if not plan:
return False
await db.delete(plan)
await db.commit()
return True
@staticmethod
async def add_kpi_relation(
db: AsyncSession,
plan_id: int,
kpi_data: PlanKpiRelationCreate
) -> Optional[PlanKpiRelation]:
"""添加计划指标关联"""
plan = await PerformancePlanService.get_by_id(db, plan_id)
if not plan:
return None
kpi_relation = PlanKpiRelation(
plan_id=plan_id,
**kpi_data.model_dump()
)
db.add(kpi_relation)
await db.commit()
await db.refresh(kpi_relation)
return kpi_relation
@staticmethod
async def update_kpi_relation(
db: AsyncSession,
relation_id: int,
kpi_data: dict
) -> Optional[PlanKpiRelation]:
"""更新计划指标关联"""
result = await db.execute(
select(PlanKpiRelation).where(PlanKpiRelation.id == relation_id)
)
kpi_relation = result.scalar_one_or_none()
if not kpi_relation:
return None
for key, value in kpi_data.items():
if hasattr(kpi_relation, key) and value is not None:
setattr(kpi_relation, key, value)
await db.commit()
await db.refresh(kpi_relation)
return kpi_relation
@staticmethod
async def delete_kpi_relation(db: AsyncSession, relation_id: int) -> bool:
"""删除计划指标关联"""
result = await db.execute(
select(PlanKpiRelation).where(PlanKpiRelation.id == relation_id)
)
kpi_relation = result.scalar_one_or_none()
if not kpi_relation:
return False
await db.delete(kpi_relation)
await db.commit()
return True
@staticmethod
async def get_stats(
db: AsyncSession,
plan_year: Optional[int] = None
) -> Dict[str, int]:
"""获取绩效计划统计"""
conditions = []
if plan_year:
conditions.append(PerformancePlan.plan_year == plan_year)
query = select(
PerformancePlan.status,
func.count().label('count')
)
if conditions:
query = query.where(and_(*conditions))
query = query.group_by(PerformancePlan.status)
result = await db.execute(query)
rows = result.fetchall()
stats = {
'total_plans': 0,
'draft_count': 0,
'pending_count': 0,
'approved_count': 0,
'active_count': 0,
'completed_count': 0
}
for row in rows:
status = row.status
count = row.count
stats['total_plans'] += count
if status == PlanStatus.DRAFT:
stats['draft_count'] = count
elif status == PlanStatus.PENDING:
stats['pending_count'] = count
elif status == PlanStatus.APPROVED:
stats['approved_count'] = count
elif status == PlanStatus.ACTIVE:
stats['active_count'] = count
elif status == PlanStatus.COMPLETED:
stats['completed_count'] = count
return stats
@staticmethod
async def get_tree(
db: AsyncSession,
plan_year: Optional[int] = None
) -> List[Dict[str, Any]]:
"""获取绩效计划树形结构"""
conditions = []
if plan_year:
conditions.append(PerformancePlan.plan_year == plan_year)
query = select(PerformancePlan).options(
selectinload(PerformancePlan.department),
selectinload(PerformancePlan.staff)
)
if conditions:
query = query.where(and_(*conditions))
query = query.order_by(PerformancePlan.plan_level, PerformancePlan.id)
result = await db.execute(query)
plans = result.scalars().all()
# 构建树形结构
plan_dict = {}
root_plans = []
for plan in plans:
plan_dict[plan.id] = {
'id': plan.id,
'plan_name': plan.plan_name,
'plan_code': plan.plan_code,
'plan_level': plan.plan_level,
'status': plan.status,
'department_name': plan.department.name if plan.department else None,
'staff_name': plan.staff.name if plan.staff else None,
'children': []
}
if plan.parent_plan_id:
if plan.parent_plan_id in plan_dict:
plan_dict[plan.parent_plan_id]['children'].append(plan_dict[plan.id])
else:
root_plans.append(plan_dict[plan.id])
return root_plans