Jelajahi Sumber

个人评分绩效
提供导出文件流返回给前端

xiexh 16 jam lalu
induk
melakukan
e1ebdc2b73

+ 20 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/hrm/control/PersonnelManagementController.java

@@ -5,6 +5,7 @@ import com.lc.ibps.base.core.util.I18nUtil;
 import com.lc.ibps.cloud.entity.APIResult;
 import com.lc.ibps.cloud.provider.GenericProvider;
 import com.lc.ibps.hrm.service.PersonnelManagementService;
+import com.lc.ibps.vo.PerformanceQueryDTO;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
@@ -14,6 +15,9 @@ import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
 import java.util.Map;
 
 /**
@@ -41,4 +45,20 @@ public class PersonnelManagementController extends GenericProvider {
         return result;
 
     }
+    @ApiOperation("个人绩效评分返回文件流")
+    @PostMapping("/dept/grjxpf")
+    public void queryGrjxpfData(HttpServletResponse response, @RequestBody PerformanceQueryDTO queryDTO) {
+        try {
+            personnelManagementService.exportPerformanceData(response, queryDTO);
+        } catch (Exception e) {
+            // 如果响应尚未提交,可以设置错误状态和错误信息
+            response.setContentType("application/json;charset=UTF-8");
+            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+            // 也可以输出一个简单的错误 JSON,但注意这不是文件下载了
+            try (PrintWriter writer = response.getWriter()) {
+                writer.write("{\"code\":400,\"message\":\"" + e.getMessage() + "\"}");
+            } catch (IOException ex) { /* log */ }
+        }
+    }
+
 }

+ 9 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/hrm/dao/PersonnelManagementDao.java

@@ -15,4 +15,13 @@ public interface PersonnelManagementDao {
 
     List<Map<String, Object>> querySnapshoot(@Param("map") Map<String, Object> map);
 
+    List<Map<String, Object>> queryPerformanceForExport(Map<String, Object> convertToMap);
+
+    List<Map<String, Object>> queryPerformanceIdsByKeys(List<Map<String, Object>> queryKeys);
+
+    void batchUpdatePerformanceScores(List<Map<String, Object>> updateBatch);
+
+    List<String> queryConfiguration(@Param("map") Map<String, Object> map);
+
+    List<Map<String, Object>> queryPartyInfo(Map<String, Object> convertToMap);
 }

+ 15 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/hrm/service/PersonnelManagementService.java

@@ -1,8 +1,13 @@
 package com.lc.ibps.hrm.service;
 
 import com.lc.ibps.cloud.entity.APIResult;
+import com.lc.ibps.vo.ImportResult;
+import com.lc.ibps.vo.PerformanceQueryDTO;
 import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.multipart.MultipartFile;
 
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
 import java.util.Map;
 
 /**
@@ -16,4 +21,14 @@ public interface PersonnelManagementService {
      */
     APIResult<Object> queryDeptSnapshot(@RequestBody(required = true) Map<String,Object> map)throws Exception ;
 
+    /**
+     * 导出绩效数据
+     */
+    void exportPerformanceData(HttpServletResponse response, PerformanceQueryDTO queryDTO) throws IOException;
+
+    /**
+     * 导入绩效数据
+     */
+    ImportResult importPerformanceData(MultipartFile file) throws IOException;
+
 }

+ 424 - 31
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/hrm/service/imple/PersonnelManagementServiceImpl.java

@@ -16,15 +16,28 @@ import com.lc.ibps.hrm.service.PersonnelManagementService;
 import com.lc.ibps.org.api.IPartyPositionService;
 import com.lc.ibps.org.party.persistence.entity.PartyPositionPo;
 import com.lc.ibps.sysdata.dao.UpdateDataTableDao;
+import com.lc.ibps.vo.ImportError;
+import com.lc.ibps.vo.ImportResult;
+import com.lc.ibps.vo.PerformanceQueryDTO;
 import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections.MapUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.hssf.usermodel.HSSFCell;
+import org.apache.poi.hssf.usermodel.HSSFRow;
+import org.apache.poi.hssf.usermodel.HSSFSheet;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.CellType;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.net.URLEncoder;
+import java.util.*;
 
 /**
  * @title: xiexh
@@ -41,17 +54,29 @@ public class PersonnelManagementServiceImpl extends GenericProvider implements P
     @Resource
     private ICommonDao<?> commonDao;
 
+    // ================== 新增:Excel 列索引常量 ==================
+    // 根据实际导出的列顺序调整 (假设为:姓名, 部门, 年份, 月份, 条款名称, 绩效评分, 备注)
+    private static final int COL_INDEX_XM = 0;
+    private static final int COL_INDEX_BM = 1;
+    private static final int COL_INDEX_NF = 2;
+    private static final int COL_INDEX_YF = 3;
+    private static final int COL_INDEX_TK = 4;
+    private static final int COL_INDEX_PF = 5;
+    private static final int COL_INDEX_BZ = 6;
+
+    // ================== 原有业务方法 (保留不变) ==================
+
     @Override
     public APIResult<Object> queryDeptSnapshot(Map<String, Object> map) throws Exception {
         APIResult<Object> result = new APIResult<>();
         try {
             int pageNo = Integer.parseInt(map.get("pageNo").toString());
             int limit = Integer.parseInt(map.get("limit").toString());
-            int startPage = limit*(pageNo-1);
-            map.put("startPage",startPage);
+            int startPage = limit * (pageNo - 1);
+            map.put("startPage", startPage);
             Map mapDeal = getMapV2(map);
-            List<Map<String,Object>> list = personnelManagementDao.querySnapshoot(mapDeal);
-            APIPageList<Map<String,Object>> pageList = getAPIPageList(list);
+            List<Map<String, Object>> list = personnelManagementDao.querySnapshoot(mapDeal);
+            APIPageList<Map<String, Object>> pageList = getAPIPageList(list);
             APIPageResult pageResult = new APIPageResult();
             pageResult.setTotalCount(1);
             pageResult.setLimit(limit);
@@ -59,44 +84,412 @@ public class PersonnelManagementServiceImpl extends GenericProvider implements P
             pageList.setPageResult(pageResult);
             result.setData(pageList);
         } catch (Exception e) {
-            setExceptionResult(result, StateEnum.ERROR_FORM_BO.getCode(), I18nUtil.getMessage(StateEnum.ERROR_FORM_BO.getCode()+""), e);
+            setExceptionResult(result, StateEnum.ERROR_FORM_BO.getCode(), I18nUtil.getMessage(StateEnum.ERROR_FORM_BO.getCode() + ""), e);
         }
         return result;
     }
 
     private Map getMapV2(Map<String, Object> map) {
-            HashMap<String, Object> stringObjectHashMap = new HashMap<>();
-
-            if (BeanUtils.isNotEmpty(map)) {
-                stringObjectHashMap.put("pageNo", map.get("pageNo"));
-                stringObjectHashMap.put("limit", map.get("limit"));
-                stringObjectHashMap.put("startPage", map.get("startPage"));
-                stringObjectHashMap.put("locationId", getDiDian());
-                if(BeanUtils.isNotEmpty(map.get("param"))){
-                    Map param = (Map) map.get("param");
-                    if(BeanUtils.isNotEmpty(param.get("gangWei"))){
-                        // 将逗号分隔的字符串拆分为List
-                        String gangweiStr= (String) param.get("gangWei");
-                        List<String> gangweiList = Arrays.asList(gangweiStr.trim().split("\\s*,\\s*"));
-                        stringObjectHashMap.put("gangWei",gangweiList);
-                    }
+        HashMap<String, Object> stringObjectHashMap = new HashMap<>();
+        if (BeanUtils.isNotEmpty(map)) {
+            stringObjectHashMap.put("pageNo", map.get("pageNo"));
+            stringObjectHashMap.put("limit", map.get("limit"));
+            stringObjectHashMap.put("startPage", map.get("startPage"));
+            stringObjectHashMap.put("locationId", getDiDian());
+            if (BeanUtils.isNotEmpty(map.get("param"))) {
+                Map param = (Map) map.get("param");
+                if (BeanUtils.isNotEmpty(param.get("gangWei"))) {
+                    // 将逗号分隔的字符串拆分为List
+                    String gangweiStr = (String) param.get("gangWei");
+                    List<String> gangweiList = Arrays.asList(gangweiStr.trim().split("\\s*,\\s*"));
+                    stringObjectHashMap.put("gangWei", gangweiList);
                 }
             }
-            return stringObjectHashMap;
-
+        }
+        return stringObjectHashMap;
     }
 
-    //获取当前用户地点,前端可以不用传
+    // 获取当前用户地点,前端可以不用传
     private String getDiDian() {
         IPartyPositionService partyPositionService = AppUtil.getBean(IPartyPositionService.class);
         APIResult<List<PartyPositionPo>> result = partyPositionService.findByUserId(ContextUtil.getCurrentUserId());
-        String diDian ="";
+        String diDian = "";
         try {
             diDian = result.getData().get(0).getPath().split(StringPool.BACK_SLASH + StringPool.DOT)[1];
-        }catch (Exception ex){
-            log.error("Can't get didian information",ex);
+        } catch (Exception ex) {
+            log.error("Can't get didian information", ex);
             return null;
         }
         return diDian;
     }
-}
+
+    // ================== 新增:1. 绩效数据导出 ==================
+    @Override
+    public void exportPerformanceData(HttpServletResponse response, PerformanceQueryDTO queryDTO) throws IOException {
+        // 1. 查询数据库数据
+        List<Map<String, Object>> rawData = personnelManagementDao.queryPerformanceForExport(convertToMap(queryDTO));
+        if (rawData == null || rawData.isEmpty()) {
+            throw new RuntimeException("未查询到相关绩效数据");
+        }
+        // 2. 构建xls输出文件流
+        constructExportSheet(rawData,response);
+
+    }
+
+    /**
+     * 构建并导出包含三个Sheet页的绩效Excel文件
+     * @param rawData 从数据库查询出的原始明细数据
+     * @param response HttpServletResponse响应对象
+     */
+    private void constructExportSheet(List<Map<String, Object>> rawData, HttpServletResponse response) throws IOException {
+        HSSFWorkbook workbook = new HSSFWorkbook();
+        Map Mapping = new HashMap();
+        //构建映射
+        List<Map<String, Object>> list = personnelManagementDao.queryPartyInfo(null);
+        for(Map map :list){
+            Mapping.putIfAbsent(map.get("ID_"),map.get("NAME_"));
+        }
+
+        // 1. 创建第一个Sheet:个人评分明细
+        createDetailSheet(workbook, rawData, Mapping);
+
+        // 2. 创建第二个Sheet:按月份和年份汇总
+        createMonthSummarySheet(workbook, rawData, Mapping);
+
+        // 3. 创建第三个Sheet:按条款汇总
+        createClauseSummarySheet(workbook, rawData, Mapping);
+
+        // 4. 输出Excel文件到浏览器
+        response.setContentType("application/vnd.ms-excel");
+        String wjmc = "个人评分汇总_" + System.currentTimeMillis() + ".xls";
+        String encodedFileName = URLEncoder.encode(wjmc, "UTF-8").replaceAll("\\+", "%20");
+        response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encodedFileName);
+
+        workbook.write(response.getOutputStream());
+        workbook.close();
+    }
+
+    /**
+     * 创建第一个Sheet:个人评分明细
+     */
+    private void createDetailSheet(HSSFWorkbook workbook, List<Map<String, Object>> rawData, Map<String, String> mapping) {
+        HSSFSheet sheet = workbook.createSheet("个人评分明细");
+        // 创建表头
+        String[] headers = {"部门", "年份", "月份", "姓名", "条款名称", "绩效评分", "备注"};
+        HSSFRow headerRow = sheet.createRow(0);
+        for (int i = 0; i < headers.length; i++) {
+            headerRow.createCell(i).setCellValue(headers[i]);
+        }
+        // 填充数据
+        for (int i = 0; i < rawData.size(); i++) {
+            Map<String, Object> rowMap = rawData.get(i);
+            HSSFRow row = sheet.createRow(i + 1);
+            String buMeng = MapUtils.getString(mapping,MapUtils.getString(rowMap, "bu_men_2_", ""), "");
+            String name = MapUtils.getString(mapping,MapUtils.getString(rowMap, "xing_ming_2_", ""), "");
+            row.createCell(0).setCellValue(buMeng);
+            row.createCell(1).setCellValue(MapUtils.getString(rowMap, "nian_fen_2_", ""));
+            row.createCell(2).setCellValue(MapUtils.getString(rowMap, "yue_fen_2_", ""));
+            row.createCell(3).setCellValue(name);
+            row.createCell(4).setCellValue(MapUtils.getString(rowMap, "tiao_kuan_ming_ch", ""));
+            row.createCell(5).setCellValue(MapUtils.getString(rowMap, "ji_xiao_ping_fen_", ""));
+            row.createCell(6).setCellValue(MapUtils.getString(rowMap, "bei_zhu_", ""));
+        }
+    }
+
+    /**
+     * 创建第二个Sheet:按月份和年份汇总
+     */
+    private void createMonthSummarySheet(HSSFWorkbook workbook, List<Map<String, Object>> rawData, Map<String, String> mapping) {
+        HSSFSheet sheet = workbook.createSheet("按月份和年份汇总");
+        // 创建表头
+        String[] headers = {"序号", "部门", "年份", "姓名", "一月", "二月", "三月", "四月", "五月", "六月",
+                "七月", "八月", "九月", "十月", "十一月", "十二月", "年份汇总"};
+        HSSFRow headerRow = sheet.createRow(0);
+        for (int i = 0; i < headers.length; i++) {
+            headerRow.createCell(i).setCellValue(headers[i]);
+        }
+
+        // 汇总逻辑:按 部门、年份、姓名 分组
+        Map<String, Map<Integer, BigDecimal>> summaryMap = new LinkedHashMap<>();
+        Map<String, String> deptYearNameMap = new LinkedHashMap<>(); // 保存部门、年份、姓名的对应关系
+
+        for (Map<String, Object> row : rawData) {
+            String dept = MapUtils.getString(row, "bu_men_2_");
+            String year = MapUtils.getString(row, "nian_fen_2_");
+            String name = MapUtils.getString(row, "xing_ming_2_");
+            String monthStr = MapUtils.getString(row, "yue_fen_2_");
+            String scoreStr = MapUtils.getString(row, "ji_xiao_ping_fen_");
+
+            BigDecimal score = StringUtils.isNotBlank(scoreStr) ? new BigDecimal(scoreStr) : BigDecimal.ZERO;
+            int month = 0;
+            try { month = Integer.parseInt(monthStr); } catch (Exception e) { continue; }
+
+            // 构建分组Key
+            String groupKey = dept + "_" + year + "_" + name;
+            deptYearNameMap.putIfAbsent(groupKey, groupKey);
+
+            // 累加对应月份的分数
+            summaryMap.computeIfAbsent(groupKey, k -> new HashMap<>()).merge(month, score, BigDecimal::add);
+        }
+
+        // 填充汇总数据到Sheet
+        int rowIndex = 1;
+        for (Map.Entry<String, Map<Integer, BigDecimal>> entry : summaryMap.entrySet()) {
+            String[] keys = entry.getKey().split("_");
+            String dept = keys[0];
+            String year = keys[1];
+            String name = keys[2];
+
+            String buMeng = MapUtils.getString(mapping,dept, dept);
+            String xingMing = MapUtils.getString(mapping,name, name);
+
+            Map<Integer, BigDecimal> monthScores = entry.getValue();
+
+            HSSFRow row = sheet.createRow(rowIndex++);
+            row.createCell(0).setCellValue(rowIndex - 1); // 序号
+            row.createCell(1).setCellValue(buMeng);
+            row.createCell(2).setCellValue(year);
+            row.createCell(3).setCellValue(xingMing);
+            BigDecimal yearTotal = BigDecimal.ZERO;
+            for (int m = 1; m <= 12; m++) {
+                BigDecimal monthScore = monthScores.getOrDefault(m, BigDecimal.ZERO);
+                row.createCell(3 + m).setCellValue(monthScore.toPlainString());
+                yearTotal = yearTotal.add(monthScore);
+            }
+            row.createCell(16).setCellValue(yearTotal.toPlainString()); // 年份汇总
+        }
+    }
+
+    /**
+     * 创建第三个Sheet:按条款汇总
+     */
+    private void createClauseSummarySheet(HSSFWorkbook workbook, List<Map<String, Object>> rawData, Map<String, String> mapping) {
+        HSSFSheet sheet = workbook.createSheet("按条款汇总");
+        List<String> clauseConfig = new ArrayList<>();
+        // 查询条款配置项
+        List<String> dbRecords = personnelManagementDao.queryConfiguration(null);
+
+        clauseConfig.addAll(dbRecords);
+        // 创建表头
+        String[] headers = new String[3 + clauseConfig.size() + 1]; // 序号 + 部门 + 年份 + 姓名 + 16个条款 + 汇总
+        headers[0] = "序号"; headers[1] = "部门"; headers[2] = "年份"; headers[3] = "姓名";
+        for (int i = 0; i < clauseConfig.size(); i++) {
+            headers[4 + i] = clauseConfig.get(i);
+        }
+        headers[headers.length - 1] = "条款分值汇总";
+
+        HSSFRow headerRow = sheet.createRow(0);
+        for (int i = 0; i < headers.length; i++) {
+            headerRow.createCell(i).setCellValue(headers[i]);
+        }
+
+        // 汇总逻辑:按 部门、年份、姓名、条款名称 分组
+        Map<String, Map<String, BigDecimal>> summaryMap = new LinkedHashMap<>();
+        Map<String, String> deptYearNameMap = new LinkedHashMap<>();
+
+        for (Map<String, Object> row : rawData) {
+            String dept = MapUtils.getString(row, "bu_men_2_");
+            String year = MapUtils.getString(row, "nian_fen_2_");
+            String name = MapUtils.getString(row, "xing_ming_2_");
+            String clauseName = MapUtils.getString(row, "tiao_kuan_ming_ch", "");
+            String scoreStr = MapUtils.getString(row, "ji_xiao_ping_fen_");
+
+            BigDecimal score = StringUtils.isNotBlank(scoreStr) ? new BigDecimal(scoreStr) : BigDecimal.ZERO;
+
+            // 如果条款不在配置的16个条款内,归为"其他条款"
+            String finalClause = clauseConfig.contains(clauseName) ? clauseName : "其他条款";
+
+            String groupKey = dept + "_" + year + "_" + name;
+            deptYearNameMap.putIfAbsent(groupKey, groupKey);
+
+            // 累加对应条款的分数
+            summaryMap.computeIfAbsent(groupKey, k -> new HashMap<>()).merge(finalClause, score, BigDecimal::add);
+        }
+
+        // 填充汇总数据到Sheet
+        int rowIndex = 1;
+        for (Map.Entry<String, Map<String, BigDecimal>> entry : summaryMap.entrySet()) {
+            String[] keys = entry.getKey().split("_");
+            String dept = keys[0];
+            String year = keys[1];
+            String name = keys[2];
+            Map<String, BigDecimal> clauseScores = entry.getValue();
+
+            String buMeng = MapUtils.getString(mapping,dept, dept);
+            String xingMing = MapUtils.getString(mapping,name, name);
+
+            HSSFRow row = sheet.createRow(rowIndex++);
+            row.createCell(0).setCellValue(rowIndex - 1); // 序号
+            row.createCell(1).setCellValue(buMeng);
+            row.createCell(2).setCellValue(year);
+            row.createCell(3).setCellValue(xingMing);
+
+            BigDecimal clauseTotal = BigDecimal.ZERO;
+            for (int i = 0; i < clauseConfig.size(); i++) {
+                String clause = clauseConfig.get(i);
+                BigDecimal score = clauseScores.getOrDefault(clause, BigDecimal.ZERO);
+                row.createCell(4 + i).setCellValue(score.toPlainString());
+                clauseTotal = clauseTotal.add(score);
+            }
+            row.createCell(4 + clauseConfig.size()).setCellValue(clauseTotal.toPlainString()); // 条款分值汇总
+        }
+    }
+
+    // ================== 新增:2. 绩效数据导入 ==================
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ImportResult importPerformanceData(MultipartFile file) throws IOException {
+        ImportResult result = new ImportResult();
+
+        // 1. 解析 Excel 数据
+        List<Map<String, Object>> excelDataList = readExcelRows(file);
+        if (excelDataList.isEmpty()) {
+            result.setFailCount(1);
+            result.getErrorList().add(new ImportError(0, "Excel文件为空或格式错误"));
+            return result;
+        }
+
+        // 2. 提取唯一键,准备去数据库匹配真实 ID
+        List<Map<String, Object>> queryKeys = new ArrayList<>();
+        Map<String, Map<String, Object>> excelDataMap = new LinkedHashMap<>(); // 保持顺序
+
+        for (Map<String, Object> excelRow : excelDataList) {
+            String uniqueKey = buildUniqueKey(excelRow);
+
+            // 构建查询参数 (对应 XML 中的批量 IN 查询)
+            Map<String, Object> keyParam = new HashMap<>();
+            keyParam.put("xing_ming_2_", excelRow.get("xing_ming_2_"));
+            keyParam.put("bu_men_2_", excelRow.get("bu_men_2_"));
+            keyParam.put("nian_fen_2_", excelRow.get("nian_fen_2_"));
+            keyParam.put("yue_fen_2_", excelRow.get("yue_fen_2_"));
+            keyParam.put("tiao_kuan_ming_ch", excelRow.get("tiao_kuan_ming_ch"));
+
+            queryKeys.add(keyParam);
+            excelDataMap.put(uniqueKey, excelRow);
+        }
+
+        // 3. 批量查询数据库中已存在的记录 ID
+        List<Map<String, Object>> dbRecords = personnelManagementDao.queryPerformanceIdsByKeys(queryKeys);
+        Map<String, String> dbIdMap = new HashMap<>();
+        for (Map<String, Object> dbRow : dbRecords) {
+            dbIdMap.put(buildUniqueKey(dbRow), MapUtils.getString(dbRow, "id_"));
+        }
+
+        // 4. 比对数据,组装更新列表
+        List<Map<String, Object>> updateBatch = new ArrayList<>();
+        int rowNum = 1; // Excel 行号,从1开始(跳过表头)
+
+        for (Map.Entry<String, Map<String, Object>> entry : excelDataMap.entrySet()) {
+            rowNum++;
+            String key = entry.getKey();
+            Map<String, Object> excelData = entry.getValue();
+
+            if (dbIdMap.containsKey(key)) {
+                // 找到匹配项,只提取允许修改的字段 + 数据库真实ID
+                Map<String, Object> updateParam = new HashMap<>();
+                updateParam.put("id_", dbIdMap.get(key));
+                updateParam.put("ji_xiao_ping_fen_", excelData.get("ji_xiao_ping_fen_"));
+                updateParam.put("bei_zhu_", excelData.get("bei_zhu_"));
+                updateBatch.add(updateParam);
+                result.setSuccessCount(result.getSuccessCount() + 1);
+            } else {
+                // 未找到匹配项
+                result.setFailCount(result.getFailCount() + 1);
+                result.getErrorList().add(new ImportError(rowNum, "未找到匹配的原始数据: " + key));
+            }
+        }
+
+        // 5. 执行数据库批量更新
+        if (!updateBatch.isEmpty()) {
+            personnelManagementDao.batchUpdatePerformanceScores(updateBatch);
+        }
+
+        return result;
+    }
+
+    // ================== 私有辅助方法 ==================
+
+    // DTO 转 Map
+    private Map<String, Object> convertToMap(PerformanceQueryDTO dto) {
+        Map<String, Object> map = new HashMap<>();
+        map.put("nianFen", dto.getNianFen());
+        map.put("yueFen", dto.getYueFen());
+        map.put("xingMing", dto.getXingMing());
+        map.put("buMen", dto.getBuMen());
+        return map;
+    }
+
+    // 解析 Excel 行数据
+    private List<Map<String, Object>> readExcelRows(MultipartFile file) throws IOException {
+        List<Map<String, Object>> list = new ArrayList<>();
+        try (HSSFWorkbook workbook = new HSSFWorkbook(file.getInputStream())) {
+            HSSFSheet sheet = workbook.getSheetAt(0);
+            // 假设第一行是表头,从第二行开始读取
+            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
+                HSSFRow row = sheet.getRow(i);
+                if (row == null) continue;
+
+                Map<String, Object> data = new HashMap<>();
+                data.put("xing_ming_2_", getCellValue(row.getCell(COL_INDEX_XM)));
+                data.put("bu_men_2_", getCellValue(row.getCell(COL_INDEX_BM)));
+                data.put("nian_fen_2_", getCellValue(row.getCell(COL_INDEX_NF)));
+                data.put("yue_fen_2_", getCellValue(row.getCell(COL_INDEX_YF)));
+                data.put("tiao_kuan_ming_ch", getCellValue(row.getCell(COL_INDEX_TK)));
+                data.put("ji_xiao_ping_fen_", getCellValue(row.getCell(COL_INDEX_PF)));
+                data.put("bei_zhu_", getCellValue(row.getCell(COL_INDEX_BZ)));
+                list.add(data);
+            }
+        }
+        return list;
+    }
+
+    // 获取单元格字符串值
+    private String getCellValue(HSSFCell cell) {
+        if (cell == null) return "";
+        cell.setCellType(CellType.STRING);
+        return cell.getStringCellValue().trim();
+    }
+
+    // 构建业务唯一键 (姓名|部门|年|月|条款)
+    private String buildUniqueKey(Map<String, Object> data) {
+        return MapUtils.getString(data, "xing_ming_2_") + "|" +
+                MapUtils.getString(data, "bu_men_2_") + "|" +
+                MapUtils.getString(data, "nian_fen_2_") + "|" +
+                MapUtils.getString(data, "yue_fen_2_") + "|" +
+                MapUtils.getString(data, "tiao_kuan_ming_ch");
+    }
+
+    // 内部类:年度汇总数据容器
+    private static class YearSummary {
+        private final String year;
+        private BigDecimal totalScore = BigDecimal.ZERO;
+        private int itemCount = 0;
+        private final List<Map<String, Object>> dataList = new ArrayList<>();
+
+        public YearSummary(String year) {
+            this.year = year;
+        }
+
+        public void addData(Map<String, Object> data, BigDecimal score) {
+            this.totalScore = this.totalScore.add(score);
+            this.itemCount++;
+            this.dataList.add(data);
+        }
+
+        public String getYear() {
+            return year;
+        }
+
+        public BigDecimal getTotalScore() {
+            return totalScore;
+        }
+
+        public int getItemCount() {
+            return itemCount;
+        }
+
+        public List<Map<String, Object>> getDataList() {
+            return dataList;
+        }
+    }
+}

+ 30 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/vo/GrjxPfb.java

@@ -0,0 +1,30 @@
+package com.lc.ibps.vo;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_grjxpfb")
+public class GrjxPfb {
+    @TableId
+    private String id_;
+    private String tenantId_;
+    private String ip_;
+    private String createBy_;
+    private LocalDateTime createTime_;
+    private String updateBy_;
+    private LocalDateTime updateTime_;
+    private String shi_fou_guo_shen_;
+    private String di_dian_;
+    private String bian_zhi_ren_;
+    private String bian_zhi_bu_men_;
+    private String bian_zhi_shi_jian;
+    private String bu_men_;
+    private String nian_fen_;
+    private String yue_fen_;
+    private String xing_ming_;
+    private String kuai_zhao_;
+}

+ 36 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/vo/GrjxPfzb.java

@@ -0,0 +1,36 @@
+package com.lc.ibps.vo;
+
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+@TableName("t_grjxpfzb")
+public class GrjxPfzb {
+    @TableId
+    private String id_;
+    private String parentId_;
+    private String tenantId_;
+    private String ip_;
+    private String createBy_;
+    private LocalDateTime createTime_;
+    private String updateBy_;
+    private LocalDateTime updateTime_;
+    private String shi_fou_guo_shen_;
+    private String di_dian_;
+    private String bian_zhi_ren_;
+    private String bian_zhi_bu_men_;
+    private String bian_zhi_shi_jian;
+    private String bu_men_2_;
+    private String nian_fen_2_;
+    private String yue_fen_2_;
+    private String xing_ming_2_;
+    private String tiao_kuan_ming_ch;
+    private String ji_xiao_ping_fen_;
+    private String bei_zhu_;
+    private String yu_liu_1_;
+    private String yu_liu_2_;
+    private String kuai_zhao_;
+}

+ 14 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/vo/ImportError.java

@@ -0,0 +1,14 @@
+package com.lc.ibps.vo;
+
+import lombok.Data;
+
+@Data
+public class ImportError {
+    private int row; // 错误行号
+    private String message; // 错误原因
+
+    public ImportError(int row, String message) {
+        this.row = row;
+        this.message = message;
+    }
+}

+ 13 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/vo/ImportResult.java

@@ -0,0 +1,13 @@
+package com.lc.ibps.vo;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+public class ImportResult {
+    private int successCount = 0;
+    private int failCount = 0;
+    private List<ImportError> errorList = new ArrayList<>();
+}

+ 11 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/vo/PerformanceQueryDTO.java

@@ -0,0 +1,11 @@
+package com.lc.ibps.vo;
+
+import lombok.Data;
+
+@Data
+public class PerformanceQueryDTO {
+    private String nianFen;   // 年份
+    private String yueFen;    // 月份
+    private String xingMing;  // 姓名
+    private String buMen;     // 部门
+}

+ 82 - 0
ibps-provider-root/modules/provider-business/src/main/resources/com/lc/ibps/klimsibps/mapping/PersonnelManagementMapper.xml

@@ -12,5 +12,87 @@
         </foreach>
         ORDER BY CREATE_TIME_ DESC
     </select>
+    <!-- ================================================================================== -->
+    <!-- 绩效管理专用 SQL 开始 -->
+    <!-- ================================================================================== -->
 
+    <!-- 1. 导出数据查询 (Export Query) -->
+    <!-- 用途:根据年份、月份、姓名、部门筛选数据,用于导出Excel -->
+    <!-- 参数:map 包含 nianFen, yueFen, xingMing, buMen -->
+    <select id="queryPerformanceForExport" parameterType="java.util.Map" resultType="java.util.Map">
+        SELECT
+        z.id_ AS z_id,
+        z.parent_id_,
+        h.nian_fen_ AS h_year,
+        h.yue_fen_ AS h_month,
+        z.xing_ming_2_,
+        z.bu_men_2_,
+        z.nian_fen_2_,
+        z.yue_fen_2_,
+        z.tiao_kuan_ming_ch,
+        z.ji_xiao_ping_fen_,
+        z.bei_zhu_
+        FROM t_grjxpfzb z
+        LEFT JOIN t_grjxpfb h ON z.parent_id_ = h.id_
+        WHERE 1=1
+        <!-- 筛选:年份 -->
+        <if test="map != null and map.nianFen != null and map.nianFen != ''">
+            AND z.nian_fen_2_ = #{map.nianFen}
+        </if>
+        <!-- 筛选:月份 -->
+        <if test="map != null and map.yueFen != null and map.yueFen != ''">
+            AND z.yue_fen_2_ = #{map.yueFen}
+        </if>
+        <!-- 筛选:姓名 -->
+        <if test="map != null and map.xingMing != null and map.xingMing != ''">
+            AND z.xing_ming_2_ LIKE CONCAT('%', #{map.xingMing}, '%')
+        </if>
+        <!-- 筛选:部门 -->
+        <if test="map != null and map.buMen != null and map.buMen != ''">
+            AND z.bu_men_2_ LIKE CONCAT('%', #{map.buMen}, '%')
+        </if>
+        ORDER BY z.nian_fen_2_ DESC, z.yue_fen_2_ DESC, z.xing_ming_2_, z.bu_men_2_
+    </select>
+
+    <!-- 2. 批量查询唯一键 (Import - Match Keys) -->
+    <!-- 用途:导入时,根据 [姓名,部门,年,月,条款] 列表批量查询数据库中已有的记录ID -->
+    <!-- 参数:list of map, 每个map包含 xing_ming_2_, bu_men_2_, nian_fen_2_, yue_fen_2_, tiao_kuan_ming_ch -->
+    <select id="queryPerformanceIdsByKeys" parameterType="java.util.List" resultType="java.util.Map">
+        SELECT
+        id_,
+        xing_ming_2_,
+        bu_men_2_,
+        nian_fen_2_,
+        yue_fen_2_,
+        tiao_kuan_ming_ch
+        FROM t_grjxpfzb
+        WHERE (xing_ming_2_, bu_men_2_, nian_fen_2_, yue_fen_2_, tiao_kuan_ming_ch) IN
+        <foreach collection="list" item="item" open="(" separator="," close=")">
+            (#{item.xing_ming_2_}, #{item.bu_men_2_}, #{item.nian_fen_2_}, #{item.yue_fen_2_}, #{item.tiao_kuan_ming_ch})
+        </foreach>
+    </select>
+
+    <!-- 3. 动态更新明细 (Import - Update) -->
+    <!-- 用途:导入时,只更新 绩效评分 和 备注 字段 -->
+    <!-- 参数:List of Map,包含 id_, ji_xiao_ping_fen_, bei_zhu_ -->
+    <update id="batchUpdatePerformanceScores" parameterType="java.util.List">
+        <foreach collection="list" item="item" separator=";">
+            UPDATE t_grjxpfzb
+            SET
+            ji_xiao_ping_fen_ = #{item.ji_xiao_ping_fen_},
+            bei_zhu_ = #{item.bei_zhu_}
+            WHERE id_ = #{item.id_}
+        </foreach>
+    </update>
+    <!-- 4. 查询条款配置 -->
+    <select id="queryConfiguration" parameterType="java.util.List" resultType="java.lang.String">
+        SELECT
+        tiao_kuan_ming_ch
+        FROM  t_grjxpzx
+        order by id_
+    </select>
+    <!-- 5. 构建用户,部门的映射 -->
+    <select id="queryPartyInfo" parameterType="java.util.List" resultType="java.util.Map">
+        select ID_,party_type_,NAME_ from ibps_party_entity where party_type_='employee' or party_type_='position'
+    </select>
 </mapper>