企业级软件研发团队绩效考核系统开发(持续更新 Day 8)

作者:呱牛

发布日期:2026年3月30日

标签:FastAPI、绩效考核、任务明细

🔥 今日亮点

2026年3月30日 - 任务明细与技术指标计算完成

  • 完成任务明细文件上传功能:新增独立上传按钮,支持《团队任务派单明细》Excel文件

  • 实现任务明细得分汇总计算:按工号聚合task_score,更新到task_total_score字段

  • 开发工作量天数计算功能:遍历C101-C403指标表,聚合work_days字段

  • 实现功能点计算功能:从*_pa_kpi_c301表聚合function_points字段

  • 开发代码行计算功能:从*_pa_kpi_c301表聚合code_lines字段

  • 修复类方法调用错误:将self改为cls,确保方法正确调用

  • 完善日志记录:生成详细的计算过程和结果日志

  • 扩展绩效考核结果表:新增task_total_score、function_points、code_lines字段

📋 文章目录

  • [🎯 任务明细与技术指标计算完成](#🎯 任务明细与技术指标计算完成)

  • [🐛 问题排查与修复](#🐛 问题排查与修复)

  • [🔧 代码优化](#🔧 代码优化)

  • [📊 完整流程](#📊 完整流程)

  • [🚀 技术要点](#🚀 技术要点)

  • [📈 待实现功能](#📈 待实现功能)

  • [🧪 测试验证](#🧪 测试验证)

  • [⚠️ 注意事项](#⚠️ 注意事项)

  • [📝 今日总结](#📝 今日总结)


🎯 任务明细与技术指标计算完成

1.1 功能需求

扩展绩效考核系统功能,支持任务明细文件上传和技术指标计算:

  • 新增任务明细文件上传按钮,支持《团队任务派单明细》Excel文件

  • 实现任务明细得分汇总计算,按工号聚合task_score

  • 开发工作量天数计算,遍历C101-C403指标表聚合work_days

  • 实现功能点计算,从*_pa_kpi_c301表聚合function_points

  • 开发代码行计算,从*_pa_kpi_c301表聚合code_lines

1.2 实现步骤

步骤1:前端页面添加任务明细上传按钮
复制代码
<!-- 在绩效明细上传按钮下方添加任务明细上传按钮 -->
<el-form-item label="任务明细" prop="task_file">
  <single-file-upload
    v-model="formData.task_file"
    :trigger-params="{
      action: `${BASE_API}/gencode/*_pa_excel_upload_record/upload_task_file`,
      headers: {
        'Authorization': `Bearer ${token}`
      }
    }"
    :accept=".xlsx,.xls"
    :limit="1"
    :file-size="50 * 1024"
    :max-count="1"
    :upload-type="'task'"
    @change="handleTaskFileChange"
  >
    <el-button type="primary" plain>任务明细:选择Excel文件(《团队任务派单明细》)</el-button>
  </single-file-upload>
</el-form-item>
步骤2:后端添加任务明细文件解析方法
复制代码
@classmethod
async def parse_task_file(cls, auth, file_path, file_name, assessment_period, data_date):
    """
    解析团队任务派单明细文件
    
    参数:
    - auth: 认证对象
    - file_path: 文件路径
    - file_name: 文件名
    - assessment_period: 考核期次
    - data_date: 数据时点
    
    返回:
    - dict: 解析结果
    """
    from app.utils.excel_util import DynamicExcelParser
    from app.utils.data_cleaning_util import DataCleaningUtil
    from ..*_pa_task_assignment_score.model import *PaTaskAssignmentScoreModel
    from ..*_pa_task_assignment_score.crud import *PaTaskAssignmentScoreCRUD
    
    # 定义列映射
    column_mapping = {
        '工号': 'staff_no',
        '姓名': 'real_name',
        '岗位': 'position',
        '部门': 'department',
        '任务编号': 'task_no',
        '任务名称': 'task_name',
        '任务类型': 'task_type',
        '任务状态': 'task_status',
        '派单时间': 'dispatch_time',
        '完成时间': 'completion_time',
        '任务时长': 'task_duration',
        '明细得分': 'task_score'
    }
    
    # 解析Excel文件
    parser = DynamicExcelParser(file_path, column_mapping)
    parsed_data = parser.parse()
    
    # 数据清洗
    cleaned_data = []
    for row in parsed_data:
        cleaned_row = DataCleaningUtil.clean_data(row)
        cleaned_row['assessment_period'] = assessment_period
        cleaned_row['data_date'] = data_date
        cleaned_data.append(cleaned_row)
    
    # 插入数据
    crud = *PaTaskAssignmentScoreCRUD(model=*PaTaskAssignmentScoreModel, auth=auth)
    inserted_count = 0
    
    for data in cleaned_data:
        try:
            await crud.create_*_pa_task_assignment_score_crud(obj_in=data)
            inserted_count += 1
        except Exception as e:
            log.error(f"插入任务明细数据失败: {str(e)}")
    
    return {
        'status': 'success',
        'message': f'任务明细文件解析完成,共插入 {inserted_count} 条记录',
        'inserted_count': inserted_count
    }
步骤3:实现任务明细得分汇总计算
复制代码
@classmethod
async def calculate_task_total_score(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
    """
    计算任务明细汇总得分
    
    参数:
    - auth: 认证对象
    - assessment_period: 考核期次
    - data_date: 数据时点
    - data_timepoint_date: 数据时点日期字符串
    - staff_infos: 员工信息列表
    
    返回:
    - dict: 计算结果
    """
    from sqlalchemy import select, func, text
    from app.config.path_conf import LOG_DIR
    from collections import defaultdict
    import datetime
    from ..*_pa_task_assignment_score.model import *PaTaskAssignmentScoreModel
    
    # 初始化所有员工的任务得分为0
    staff_scores = defaultdict(float)
    for staff in staff_infos:
        staff_no = staff[0]
        staff_scores[staff_no] = 0.0
    
    # 构建查询,按工号聚合task_score
    stmt = select(
        *PaTaskAssignmentScoreModel.staff_no,
        func.sum(*PaTaskAssignmentScoreModel.task_score).label('total_score')
    ).where(
        *PaTaskAssignmentScoreModel.assessment_period == assessment_period,
        *PaTaskAssignmentScoreModel.data_date == data_date
    ).group_by(
        *PaTaskAssignmentScoreModel.staff_no
    )
    
    # 执行查询
    result = await auth.db.execute(stmt)
    rows = result.all()
    
    # 构建得分字典
    task_scores = {}
    for row in rows:
        staff_no = row.staff_no
        total_score = float(row.total_score or 0)
        task_scores[staff_no] = total_score
    
    # 累加到员工得分
    for staff_no, score in task_scores.items():
        staff_scores[staff_no] = score
    
    # 执行UPDATE语句
    update_count = 0
    for staff_no, total_score in staff_scores.items():
        update_sql = f"""
UPDATE `*_pa_performance_assessment` 
SET `task_total_score` = {total_score:.2f}, 
    `updated_time` = NOW() 
WHERE `staff_no` = '{staff_no}' 
  AND `assessment_period` = '{assessment_period}' 
  AND `data_timepoint` = '{data_timepoint_date}';
"""
        try:
            await auth.db.execute(text(update_sql))
            update_count += 1
        except Exception as e:
            log.error(f"更新员工 {staff_no} 的任务得分失败: {str(e)}")
    
    # 使用flush而不是commit,让上下文管理器自动处理事务提交
    await auth.db.flush()
    
    return {
        'status': 'success',
        'message': f'任务明细汇总得分计算完成,共更新 {update_count} 条记录',
        'updated_count': update_count
    }
步骤4:实现工作量天数计算
复制代码
@classmethod
async def _calculate_workload_days(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
    """
    计算工作量天数汇总
    
    参数:
    - auth: 认证对象
    - assessment_period: 考核期次
    - data_date: 数据时点
    - data_timepoint_date: 数据时点日期字符串
    - staff_infos: 员工信息列表
    
    返回:
    - dict: 计算结果
    """
    from datetime import datetime
    from collections import defaultdict
    from sqlalchemy import select, func, text
    from app.config.path_conf import LOG_DIR
    
    # 初始化所有员工的工作量天数为0
    staff_workload = defaultdict(float)
    for staff in staff_infos:
        staff_no = staff[0]
        staff_workload[staff_no] = 0.0
    
    # 定义需要检查的指标表
    indicator_tables = [
        '*_pa_kpi_c101', '*_pa_kpi_c102', '*_pa_kpi_c103',
        '*_pa_kpi_c105', '*_pa_kpi_c106', '*_pa_kpi_c107',
        '*_pa_kpi_c201', '*_pa_kpi_c202', '*_pa_kpi_c203',
        '*_pa_kpi_c204', '*_pa_kpi_c205', '*_pa_kpi_c206',
        '*_pa_kpi_c207', '*_pa_kpi_c208', '*_pa_kpi_c209',
        '*_pa_kpi_c210', '*_pa_kpi_c211', '*_pa_kpi_c212',
        '*_pa_kpi_c213', '*_pa_kpi_c214', '*_pa_kpi_c301',
        '*_pa_kpi_c401', '*_pa_kpi_c402', '*_pa_kpi_c403'
    ]
    
    # 遍历每个指标表
    for table_name in indicator_tables:
        try:
            # 动态导入模型
            import importlib
            module_path = f'app.plugin.module_gencode.{table_name}.model'
            module = importlib.import_module(module_path)
            # 生成正确的模型类名(驼峰命名)
            model_name_parts = table_name.split('_')
            model_class_name = ''.join([part.capitalize() for part in model_name_parts]) + 'Model'
            model_class = getattr(module, model_class_name)
            
            # 检查模型是否有work_days字段
            if hasattr(model_class, 'work_days'):
                # 构建查询,按工号聚合work_days
                stmt = select(
                    model_class.staff_no,
                    func.sum(model_class.work_days).label('total_work_days')
                ).where(
                    model_class.assessment_period == assessment_period,
                    model_class.data_date == data_date
                ).group_by(
                    model_class.staff_no
                )
                
                # 执行查询
                result = await auth.db.execute(stmt)
                rows = result.all()
                
                # 累加到员工工作量
                for row in rows:
                    staff_no = row.staff_no
                    total_work_days = float(row.total_work_days or 0)
                    staff_workload[staff_no] += total_work_days
        except Exception as e:
            log.error(f"处理表 {table_name} 失败: {str(e)}")
    
    # 执行UPDATE语句
    update_count = 0
    for staff_no, total_work_days in staff_workload.items():
        update_sql = f"""
UPDATE `*_pa_performance_assessment` 
SET `workload_days` = {total_work_days:.2f}, 
    `updated_time` = NOW() 
WHERE `staff_no` = '{staff_no}' 
  AND `assessment_period` = '{assessment_period}' 
  AND `data_timepoint` = '{data_timepoint_date}';
"""
        try:
            await auth.db.execute(text(update_sql))
            update_count += 1
        except Exception as e:
            log.error(f"更新员工 {staff_no} 的工作量天数失败: {str(e)}")
    
    # 使用flush而不是commit,让上下文管理器自动处理事务提交
    await auth.db.flush()
    
    return {
        'status': 'success',
        'message': f'工作量天数计算完成,共更新 {update_count} 条记录',
        'update_count': update_count
    }
步骤5:实现功能点计算
复制代码
@classmethod
async def _calculate_function_points(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
    """
    计算功能点汇总
    
    参数:
    - auth: 认证对象
    - assessment_period: 考核期次
    - data_date: 数据时点
    - data_timepoint_date: 数据时点日期字符串
    - staff_infos: 员工信息列表
    
    返回:
    - dict: 计算结果
    """
    from sqlalchemy import select, func, text
    from app.config.path_conf import LOG_DIR
    from collections import defaultdict
    import datetime
    
    # 初始化所有员工的功能点为0
    staff_function_points = defaultdict(int)
    for staff in staff_infos:
        staff_no = staff[0]
        staff_function_points[staff_no] = 0
    
    # 定义需要检查的指标表
    indicator_tables = [
        '*_pa_kpi_c301'
        # 未来可以添加更多表
    ]
    
    # 遍历每个指标表
    for table_name in indicator_tables:
        try:
            # 动态导入模型
            import importlib
            module_path = f'app.plugin.module_gencode.{table_name}.model'
            module = importlib.import_module(module_path)
            # 生成正确的模型类名(驼峰命名)
            model_name_parts = table_name.split('_')
            model_class_name = ''.join([part.capitalize() for part in model_name_parts]) + 'Model'
            model_class = getattr(module, model_class_name)
            
            # 检查模型是否有function_points字段
            if hasattr(model_class, 'function_points'):
                # 构建查询,按工号聚合function_points
                stmt = select(
                    model_class.staff_no,
                    func.sum(model_class.function_points).label('total_function_points')
                ).where(
                    model_class.assessment_period == assessment_period,
                    model_class.data_date == data_date
                ).group_by(
                    model_class.staff_no
                )
                
                # 执行查询
                result = await auth.db.execute(stmt)
                rows = result.all()
                
                # 累加到员工功能点
                for row in rows:
                    staff_no = row.staff_no
                    total_function_points = int(row.total_function_points or 0)
                    staff_function_points[staff_no] += total_function_points
        except Exception as e:
            log.error(f"处理表 {table_name} 失败: {str(e)}")
    
    # 执行UPDATE语句
    update_count = 0
    for staff_no, total_function_points in staff_function_points.items():
        update_sql = f"""
UPDATE `*_pa_performance_assessment` 
SET `function_points` = {total_function_points}, 
    `updated_time` = NOW() 
WHERE `staff_no` = '{staff_no}' 
  AND `assessment_period` = '{assessment_period}' 
  AND `data_timepoint` = '{data_timepoint_date}';
"""
        try:
            await auth.db.execute(text(update_sql))
            update_count += 1
        except Exception as e:
            log.error(f"更新员工 {staff_no} 的功能点失败: {str(e)}")
    
    # 使用flush而不是commit,让上下文管理器自动处理事务提交
    await auth.db.flush()
    
    return {
        'status': 'success',
        'message': f'功能点计算完成,共更新 {update_count} 条记录',
        'update_count': update_count
    }
步骤6:实现代码行计算
复制代码
@classmethod
async def _calculate_code_lines(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
    """
    计算代码行汇总
    
    参数:
    - auth: 认证对象
    - assessment_period: 考核期次
    - data_date: 数据时点
    - data_timepoint_date: 数据时点日期字符串
    - staff_infos: 员工信息列表
    
    返回:
    - dict: 计算结果
    """
    from sqlalchemy import select, func, text
    from app.config.path_conf import LOG_DIR
    from collections import defaultdict
    import datetime
    
    # 初始化所有员工的代码行为0
    staff_code_lines = defaultdict(int)
    for staff in staff_infos:
        staff_no = staff[0]
        staff_code_lines[staff_no] = 0
    
    # 定义需要检查的指标表
    indicator_tables = [
        '*_pa_kpi_c301'
        # 未来可以添加更多表
    ]
    
    # 遍历每个指标表
    for table_name in indicator_tables:
        try:
            # 动态导入模型
            import importlib
            module_path = f'app.plugin.module_gencode.{table_name}.model'
            module = importlib.import_module(module_path)
            # 生成正确的模型类名(驼峰命名)
            model_name_parts = table_name.split('_')
            model_class_name = ''.join([part.capitalize() for part in model_name_parts]) + 'Model'
            model_class = getattr(module, model_class_name)
            
            # 检查模型是否有code_lines字段
            if hasattr(model_class, 'code_lines'):
                # 构建查询,按工号聚合code_lines
                stmt = select(
                    model_class.staff_no,
                    func.sum(model_class.code_lines).label('total_code_lines')
                ).where(
                    model_class.assessment_period == assessment_period,
                    model_class.data_date == data_date
                ).group_by(
                    model_class.staff_no
                )
                
                # 执行查询
                result = await auth.db.execute(stmt)
                rows = result.all()
                
                # 累加到员工代码行
                for row in rows:
                    staff_no = row.staff_no
                    total_code_lines = int(row.total_code_lines or 0)
                    staff_code_lines[staff_no] += total_code_lines
        except Exception as e:
            log.error(f"处理表 {table_name} 失败: {str(e)}")
    
    # 执行UPDATE语句
    update_count = 0
    for staff_no, total_code_lines in staff_code_lines.items():
        update_sql = f"""
UPDATE `*_pa_performance_assessment` 
SET `code_lines` = {total_code_lines}, 
    `updated_time` = NOW() 
WHERE `staff_no` = '{staff_no}' 
  AND `assessment_period` = '{assessment_period}' 
  AND `data_timepoint` = '{data_timepoint_date}';
"""
        try:
            await auth.db.execute(text(update_sql))
            update_count += 1
        except Exception as e:
            log.error(f"更新员工 {staff_no} 的代码行失败: {str(e)}")
    
    # 使用flush而不是commit,让上下文管理器自动处理事务提交
    await auth.db.flush()
    
    return {
        'status': 'success',
        'message': f'代码行计算完成,共更新 {update_count} 条记录',
        'update_count': update_count
    }
步骤7:集成到考核期次得分计算流程
复制代码
@classmethod
async def calculate_*_pa_excel_upload_record_service(cls, auth, id):
    """
    计算考核期次得分
    
    参数:
    - auth: 认证对象
    - id: 上传记录ID
    
    返回:
    - dict: 计算结果
    """
    # 省略部分代码...
    
    # 计算任务明细汇总得分
    task_result = await cls.calculate_task_total_score(
        auth=auth, 
        assessment_period=assessment_period, 
        data_date=data_date, 
        data_timepoint_date=data_timepoint_date, 
        staff_infos=staff_infos
    )
    log.info(f"任务明细汇总得分计算结果: {task_result}")
    
    # 计算工作量天数
    workload_result = await cls._calculate_workload_days(
        auth=auth, 
        assessment_period=assessment_period, 
        data_date=data_date, 
        data_timepoint_date=data_timepoint_date, 
        staff_infos=staff_infos
    )
    log.info(f"工作量天数计算结果: {workload_result}")
    
    # 计算功能点汇总
    function_points_result = await cls._calculate_function_points(
        auth=auth, 
        assessment_period=assessment_period, 
        data_date=data_date, 
        data_timepoint_date=data_timepoint_date, 
        staff_infos=staff_infos
    )
    log.info(f"功能点计算结果: {function_points_result}")
    
    # 计算代码行汇总
    code_lines_result = await cls._calculate_code_lines(
        auth=auth, 
        assessment_period=assessment_period, 
        data_date=data_date, 
        data_timepoint_date=data_timepoint_date, 
        staff_infos=staff_infos
    )
    log.info(f"代码行计算结果: {code_lines_result}")
    
    # 计算分类汇总得分
    await cls._calculate_category_totals(
        auth=auth,
        assessment_period=assessment_period,
        data_timepoint_date=data_timepoint_date,
        staff_infos=staff_infos
    )
    
    # 更新计算状态
    update_data = {
        'calculation_status': '已生成'
    }
    await *PaExcelUploadRecordCRUD(auth).update_*_pa_excel_upload_record_crud(id=id, data=update_data)
    
    # 省略部分代码...

🐛 问题排查与修复

2.1 类方法调用错误

问题描述
复制代码
NameError: name 'self' is not defined
问题分析
  • calculate_*_pa_excel_upload_record_service 是一个类方法,使用了 self 来调用其他方法

  • 类方法应该使用 cls 而不是 self

  • _calculate_workload_days_calculate_function_points 方法未定义为类方法

解决方案
复制代码
# 1. 修改方法调用
workload_result = await cls._calculate_workload_days(...)
function_points_result = await cls._calculate_function_points(...)
code_lines_result = await cls._calculate_code_lines(...)

# 2. 修改方法定义
@classmethod
async def _calculate_workload_days(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
    pass

@classmethod
async def _calculate_function_points(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
    pass

@classmethod
async def _calculate_code_lines(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos):
    pass

2.2 模型类名生成错误

问题描述
复制代码
AttributeError: module 'app.plugin.module_gencode.*_pa_kpi_c102.model' has no attribute '*_pa_kpi_c102Model'
问题分析
  • 模型类名生成错误,使用了 *_pa_kpi_c102Model 而非正确的 *PaKpiC102Model

  • 模型类名应该使用驼峰命名法

解决方案
复制代码
# 生成正确的模型类名(驼峰命名)
model_name_parts = table_name.split('_')
model_class_name = ''.join([part.capitalize() for part in model_name_parts]) + 'Model'
model_class = getattr(module, model_class_name)

2.3 字段缺失处理

问题描述

当指标表没有特定字段时(如work_days、function_points、code_lines),会导致错误。

解决方案
复制代码
# 检查模型是否有特定字段
if hasattr(model_class, 'work_days'):
    # 处理work_days字段
    pass

if hasattr(model_class, 'function_points'):
    # 处理function_points字段
    pass

if hasattr(model_class, 'code_lines'):
    # 处理code_lines字段
    pass

🔧 代码优化

3.1 统一指标处理方法

优化前
复制代码
# 为每个指标编写单独的处理逻辑
def calculate_workload_days():
    # 处理逻辑
    pass

def calculate_function_points():
    # 处理逻辑
    pass

def calculate_code_lines():
    # 处理逻辑
    pass
优化后
复制代码
# 统一的指标处理方法
@classmethod
async def _calculate_metric(cls, auth, assessment_period, data_date, data_timepoint_date, staff_infos, metric_name, field_name):
    """
    通用指标计算方法
    
    参数:
    - auth: 认证对象
    - assessment_period: 考核期次
    - data_date: 数据时点
    - data_timepoint_date: 数据时点日期字符串
    - staff_infos: 员工信息列表
    - metric_name: 指标名称(如'work_days'、'function_points'、'code_lines')
    - field_name: 目标字段名(如'workload_days'、'function_points'、'code_lines')
    
    返回:
    - dict: 计算结果
    """
    # 统一处理逻辑
    pass

3.2 日志记录优化

优化前
复制代码
# 简单日志记录
log.info(f"计算完成,共更新 {update_count} 条记录")
优化后
复制代码
# 详细日志记录,包括原始数据和计算结果
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_path = LOG_DIR / f"{metric_name}计算{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.log"

with open(log_path, 'w', encoding='utf-8') as f:
    f.write(f"查询时间: {current_time}\n")
    f.write(f"考核期次: {assessment_period}\n")
    f.write(f"数据时点: {data_date}\n\n")
    
    f.write("=" * 80 + "\n")
    f.write(f"{metric_name}汇总:\n")
    f.write("=" * 80 + "\n")
    
    for staff_no, total_value in staff_values.items():
        f.write(f"工号: {staff_no}, {metric_name}: {total_value}\n")

3.3 异常处理优化

优化前
复制代码
# 简单异常处理
try:
    # 处理逻辑
except Exception as e:
    log.error(f"处理失败: {str(e)}")
优化后
复制代码
# 详细异常处理,继续处理其他员工
try:
    # 处理逻辑
except Exception as e:
    log.error(f"处理员工 {staff_no} 失败: {str(e)}")
    # 继续处理其他员工,不因单个错误而中断

📊 完整流程

4.1 任务明细与技术指标计算流程图

复制代码
开始
  ↓
点击"计算考核期次得分"按钮
  ↓
查询员工基本信息(*_user_staff_info)
  ↓
写入员工信息日志
  ↓
计算任务明细汇总得分
  ↓
  ↓  从*_pa_task_assignment_score表查询数据
  ↓  按工号聚合task_score
  ↓  更新task_total_score字段
  ↓
计算工作量天数
  ↓
  ↓  遍历C101-C403指标表
  ↓  检查work_days字段
  ↓  按工号聚合work_days
  ↓  更新workload_days字段
  ↓
计算功能点汇总
  ↓
  ↓  从*_pa_kpi_c301表查询数据
  ↓  检查function_points字段
  ↓  按工号聚合function_points
  ↓  更新function_points字段
  ↓
计算代码行汇总
  ↓
  ↓  从*_pa_kpi_c301表查询数据
  ↓  检查code_lines字段
  ↓  按工号聚合code_lines
  ↓  更新code_lines字段
  ↓
计算分类汇总得分
  ↓
  ↓  需求工作汇总得分
  ↓  项目工作汇总得分
  ↓  自主研发汇总得分
  ↓  运维工作汇总得分
  ↓  汇总总得分
  ↓
更新计算状态为"已生成"
  ↓
返回计算结果
  ↓
结束

4.2 数据流转图

复制代码
┌─────────────────────────────────────────────────────────────┐
│  数据源:Excel文件上传                                    │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  *_pa_excel_upload_record                    │ │
│  │  - assessment_period: 202603                   │ │
│  │  - data_date: 2026-03-23                      │ │
│  │  - calculation_status: 未计算 → 已计算              │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────┐
│  员工信息表:*_user_staff_info                      │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  staff_no, real_name, position, dept_level1_name,  │ │
│  │  dept_level2_name, team_name                     │ │
│  │  data_status = 1 (在职)                          │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────┐
│  任务明细表:*_pa_task_assignment_score            │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  staff_no, task_no, task_name, task_score,        │ │
│  │  assessment_period, data_date                     │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────┐
│  指标数据表:*_pa_kpi_*                           │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  C101-C107, C201-C214, C301, C401-C403          │ │
│  │  staff_no, work_days, function_points, code_lines  │ │
│  │  assessment_period, data_date                     │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
                                │
                                ▼
┌─────────────────────────────────────────────────────────────┐
│  绩效考核结果表:*_pa_performance_assessment        │
│  ┌───────────────────────────────────────────────────────┐ │
│  │  员工基本信息 + 指标得分                          │ │
│  │  task_total_score = 任务明细汇总得分              │ │
│  │  workload_days = 工作量天数汇总                  │ │
│  │  function_points = 功能点汇总                    │ │
│  │  code_lines = 代码行汇总                        │ │
│  │  demand_total_score = 需求工作汇总               │ │
│  │  project_total_score = 项目工作汇总               │ │
│  │  self_dev_total_score = 自主研发汇总              │ │
│  │  ops_total_score = 运维工作汇总                 │ │
│  │  total_score = 总得分                          │ │
│  └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

🚀 技术要点

5.1 动态模型导入与字段检查

复制代码
# 动态导入模型
import importlib
module_path = f'app.plugin.module_gencode.{table_name}.model'
module = importlib.import_module(module_path)
# 生成正确的模型类名(驼峰命名)
model_name_parts = table_name.split('_')
model_class_name = ''.join([part.capitalize() for part in model_name_parts]) + 'Model'
model_class = getattr(module, model_class_name)

# 检查模型是否有特定字段
if hasattr(model_class, 'work_days'):
    # 处理work_days字段
    pass

优势:

  • 支持任意指标表,无需为每个指标编写单独的处理逻辑

  • 提高代码的通用性和可维护性

  • 便于扩展新的指标类型

5.2 数据聚合与分组

复制代码
# 构建查询,按工号聚合
stmt = select(
    model_class.staff_no,
    func.sum(model_class.function_points).label('total_function_points')
).where(
    model_class.assessment_period == assessment_period,
    model_class.data_date == data_date
).group_by(
    model_class.staff_no
)

# 执行查询
result = await auth.db.execute(stmt)
rows = result.all()

优势:

  • 使用SQL聚合函数提高查询效率

  • 减少数据传输和处理开销

  • 代码简洁易读

5.3 批量更新优化

复制代码
# 执行UPDATE语句
update_count = 0
for staff_no, total_value in staff_values.items():
    update_sql = f"""
UPDATE `*_pa_performance_assessment` 
SET `{field_name}` = {total_value}, 
    `updated_time` = NOW() 
WHERE `staff_no` = '{staff_no}' 
  AND `assessment_period` = '{assessment_period}' 
  AND `data_timepoint` = '{data_timepoint_date}';
"""
    try:
        await auth.db.execute(text(update_sql))
        update_count += 1
    except Exception as e:
        log.error(f"更新员工 {staff_no} 失败: {str(e)}")

# 使用flush而不是commit,让上下文管理器自动处理事务提交
await auth.db.flush()

优势:

  • 减少事务开销

  • 提高更新效率

  • 便于错误处理和日志记录

5.4 日志管理

复制代码
# 写入日志
current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
log_path = LOG_DIR / f"{metric_name}计算{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.log"

with open(log_path, 'w', encoding='utf-8') as f:
    f.write(f"查询时间: {current_time}\n")
    f.write(f"考核期次: {assessment_period}\n")
    f.write(f"数据时点: {data_date}\n\n")
    
    f.write("=" * 80 + "\n")
    f.write(f"{metric_name}汇总:\n")
    f.write("=" * 80 + "\n")
    
    for staff_no, total_value in staff_values.items():
        f.write(f"工号: {staff_no}, {metric_name}: {total_value}\n")

优势:

  • 详细记录计算过程和结果

  • 便于排查问题

  • 提供审计跟踪


📝 今日总结

6.1 完成功能

  1. 任务明细文件上传功能完整实现

  2. 任务明细得分汇总计算功能实现

  3. 工作量天数计算功能实现

  4. 功能点计算功能实现

  5. 代码行计算功能实现

  6. 类方法调用错误修复

  7. 模型类名生成错误修复

  8. 字段缺失处理优化

  9. 日志记录完善

  10. 绩效考核结果表扩展

6.2 技术收获

  1. 掌握动态模型导入和字段检查技术

  2. 学会使用SQL聚合函数进行数据分组

  3. 理解批量更新优化策略

  4. 掌握事务管理和错误处理技巧

  5. 提高代码的通用性和可维护性

  6. 理解NULL值处理和类型转换

相关推荐
阿kun要赚马内2 小时前
Python面向对象:@property装饰器
开发语言·前端·python
测试19982 小时前
Python+Excel读取和存储测试数据完成接口自动化测试
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试
曲幽2 小时前
FastAPI + Celery 实战:异步任务的坑与解法,我帮你踩了一遍
redis·python·fastapi·web·async·celery·background·task·queue
深蓝海拓2 小时前
使用@property将类方法包装为属性
开发语言·python
I'm Jie3 小时前
FastAPI 集成 Redis 开发手册
redis·fastapi
福运常在3 小时前
股票数据API(19)次新股池数据
java·python·maven
多看书少吃饭3 小时前
Vue3 + Java + Python 打造企业级大模型知识库(含 SSE 流式对话完整源码)
java·python·状态模式
Z.风止4 小时前
Large Model-learning(2)
开发语言·笔记·python·leetcode
蓝天守卫者联盟14 小时前
玩具喷涂废气治理厂家:行业现状、技术路径与选型指南
大数据·运维·人工智能·python