""" 绩效计划服务层 """ 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