yuanzhi_kuang 2 тижнів тому
батько
коміт
fb016c7c52

+ 4 - 21
websrc/com/kingdee/eas/hr/emp/web/handler/EmployeeListHandlerEx.java

@@ -17,27 +17,10 @@ import java.io.IOException;
 import java.util.Map;
 
 /**
- * 员工列表 Handler 二开(extends 标品 EmployeeListHandler,不覆盖同名 Factory 类)。
- * <p><b>状态:代码已编写,尚未在环境中验证 HandlerEx 是否被 SHR 框架自动加载。</b></p>
- *
- * <h3>作用</h3>
- * 履历 Word 导出时,将 {@link #generateWordResumeFileBytes} 中的服务实现
- * 从标品 Factory 改为 {@link ResumeWordTemplateServiceImplEx},以启用 PersonFamily 排序等逻辑。
- *
- * <h3>覆盖的标品入口</h3>
- * <ul>
- *   <li>printResumeWordByPersonAction</li>
- *   <li>printResumeWordByAdminAction</li>
- * </ul>
- * 上述 Action 最终都会调用 generateWordResumeFileBytes。
- *
- * <h3>【待您确认 / 部署检查】</h3>
- * <ol>
- *   <li>SHR 是否自动用 EmployeeListHandlerEx 替换 EmployeeListHandler?(请对照项目中其他 HandlerEx 的加载方式)</li>
- *   <li>编译产物是否已部署到 Web 应用 WEB-INF/classes,且 classloader 优先于 employee.jar?</li>
- *   <li>generateResumeWordDocument 第 5 参数 scheme 固定传 "001",【待确认】是否与你们结构配置方案编号一致?</li>
- *   <li>calNeedTimeAction 等未覆盖的方法仍走标品 Factory,不受本扩展影响(一般无影响)</li>
- * </ol>
+ * 员工列表 Handler
+ * 202605
+ * 主要因为导出员工履历时,需要使用 ResumeWordTemplateServiceImplEx。`
+ * coyle
  */
 public class EmployeeListHandlerEx extends EmployeeListHandler {
 

+ 308 - 33
websrc/com/kingdee/eas/hr/emp/web/handler/wordtemplate/service/impl/ResumeWordTemplateServiceImplEx.java

@@ -22,26 +22,32 @@ import java.util.Map;
 
 /**
  * 员工履历 Word 导出 — 服务层二开扩展。
- * <p>
- * 按 familyRelation.number(与本人关系基础资料编码 FNumber)升序排序。
- * personFamily.relation.number 在 SQL 中会映射为 FRelationID(内码),不能用于按编码排序。
- * </p>
+ * coyle
+ * 202605
+ *   PersonFamily:按 familyRelation.number(基础资料编码 FNumber)升序排序
+ *   ygzzk(年度考核结果):按 kaoheshij 降序取最新 3 条,升序合并为单行;考核时间展示为 yyyy年
  */
 public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceImpl {
 
     private static final Logger LOGGER = LoggerFactory.getLogger(ResumeWordTemplateServiceImplEx.class);
 
-    /** Word / 结构配置中 PersonFamily 实体别名(匹配时忽略大小写) */
     private static final String PERSON_FAMILY_ENTITY = "PERSONFAMILY";
-
-    /**
-     * 与本人关系编码排序字段。
-     * SQL 中 personFamily.relation.number → PERSONFAMILY.FRelationID(UUID),不是编码;
-     * familyRelation.number → T_HR_BDRelation.FNumber(01/02/07777),与 Word 中 familyRelation.name 同源。
-     */
     private static final String PERSON_FAMILY_SORT_FIELD = "familyRelation.number";
 
-    /** multiNotOne 内存排序临时键,不写入 Word */
+    /** 年度考核结果表体(结构配置 clientNumber) */
+    private static final String YGZZK_ENTITY = "YGZZK";
+    private static final String YGZZK_SORT_FIELD = "kaoheshij";
+    private static final int YGZZK_ROW_LIMIT = 3;
+    private static final String YGZZK_SEGMENT_SEPARATOR = ",";
+    private static final String YGZZK_YEAR_SUFFIX = "年";
+
+    /** 记录内字段拼接顺序(模板未出现的字段自动跳过) */
+    private static final String[] YGZZK_FIELD_ORDER = {
+            "kaoheshij",
+            "kaohe2.name",
+            "kaohezzwenb"
+    };
+
     private static final String ROW_SORT_VALUE = "__sortValue";
 
     private static final int PERSON_FAMILY_ROW_LIMIT = 0;
@@ -67,7 +73,9 @@ public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceIm
             Map<String, Object> resultMap,
             Map<String, StructureConfigColumnsInfo> columnsInfoMap
     ) throws EASBizException, BOSException, ShrWebBizException, SQLException {
-        if (containsPersonFamilyEntity(sqlParam)) {
+        if (containsYgzzkEntity(sqlParam)) {
+            prepareYgzzkSortBeforeQuery(sqlParam);
+        } else if (containsPersonFamilyEntity(sqlParam)) {
             LOGGER.info("ResumeWordTemplateServiceImplEx.rowHandle: apply PersonFamily sort before query, field="
                     + PERSON_FAMILY_SORT_FIELD);
             preparePersonFamilySortBeforeQuery(sqlParam);
@@ -78,11 +86,11 @@ public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceIm
     @Override
     protected void sorterHandler(Context ctx, SqlParam param) {
         super.sorterHandler(ctx, param);
-        applyPersonFamilySorter(param);
-    }
-
-    private void preparePersonFamilySortBeforeQuery(SqlParam param) {
-        applyPersonFamilySorter(param);
+        if (containsYgzzkEntity(param)) {
+            applyYgzzkSorter(param);
+        } else {
+            applyPersonFamilySorter(param);
+        }
     }
 
     @Override
@@ -93,12 +101,90 @@ public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceIm
             Map<String, String> selectKeyToWordKeyMap,
             Map<String, Object> resultMap,
             Map<String, StructureConfigColumnsInfo> columnsInfoMap
+    ) throws SQLException {
+        if (containsYgzzkEntity(sqlParam)) {
+            handleYgzzkMultiNotOne(rowSet, ctx, sqlParam, selectKeyToWordKeyMap, resultMap, columnsInfoMap);
+            return;
+        }
+        if (containsPersonFamilyEntity(sqlParam)) {
+            handlePersonFamilyMultiNotOne(rowSet, ctx, sqlParam, selectKeyToWordKeyMap, resultMap, columnsInfoMap);
+            return;
+        }
+        super.multiNotOne(rowSet, ctx, sqlParam, selectKeyToWordKeyMap, resultMap, columnsInfoMap);
+    }
+
+    private void handleYgzzkMultiNotOne(
+            IRowSet rowSet,
+            Context ctx,
+            SqlParam sqlParam,
+            Map<String, String> selectKeyToWordKeyMap,
+            Map<String, Object> resultMap,
+            Map<String, StructureConfigColumnsInfo> columnsInfoMap
+    ) throws SQLException {
+        String sortSelectKey = resolveSelectKeyByFieldSuffix(selectKeyToWordKeyMap, YGZZK_SORT_FIELD);
+        List<String> orderedWordKeys = buildYgzzkOrderedWordKeys(selectKeyToWordKeyMap);
+        List<Map<String, String>> rowList = new ArrayList<Map<String, String>>();
+
+        while (rowSet.next()) {
+            Map<String, String> rowData = new HashMap<String, String>();
+            for (Map.Entry<String, String> entry : selectKeyToWordKeyMap.entrySet()) {
+                if (!isYgzzkSelectKey(entry.getKey())) {
+                    continue;
+                }
+                String wordKey = entry.getValue();
+                Object dbValue = rowSet.getObject(entry.getKey());
+                String displayValue = dataTypeHandle(ctx, dbValue, columnsInfoMap.get(wordKey));
+                if (matchesYgzzkField(entry.getKey(), YGZZK_SORT_FIELD)) {
+                    displayValue = formatYgzzkKaoheshij(displayValue);
+                }
+                if (!StringUtils.isEmpty(displayValue)) {
+                    rowData.put(wordKey, displayValue);
+                }
+            }
+            if (sortSelectKey != null) {
+                rowData.put(ROW_SORT_VALUE, resolveRowSortValue(rowSet, sortSelectKey));
+            }
+            if (!rowData.isEmpty()) {
+                rowList.add(rowData);
+            }
+        }
+
+        if (sortSelectKey != null) {
+            sortRowListDesc(rowList);
+            limitRowList(rowList, YGZZK_ROW_LIMIT);
+            sortRowListAsc(rowList);
+        } else {
+            LOGGER.warn("ResumeWordTemplateServiceImplEx.handleYgzzkMultiNotOne: sortSelectKey missing for "
+                    + YGZZK_SORT_FIELD);
+            limitRowList(rowList, YGZZK_ROW_LIMIT);
+        }
+
+        String merged = mergeYgzzkSegments(rowList, orderedWordKeys);
+        if (StringUtils.isEmpty(merged) || orderedWordKeys.isEmpty()) {
+            return;
+        }
+
+        Map<String, String> singleRow = new HashMap<String, String>();
+        String primaryWordKey = orderedWordKeys.get(0);
+        singleRow.put(primaryWordKey, merged);
+        resultMap.putAll(singleRow);
+
+        LOGGER.info("ResumeWordTemplateServiceImplEx.handleYgzzkMultiNotOne: merged into wordKey="
+                + primaryWordKey + ", value=[" + merged + "], sourceRows=" + rowList.size());
+    }
+
+    private void handlePersonFamilyMultiNotOne(
+            IRowSet rowSet,
+            Context ctx,
+            SqlParam sqlParam,
+            Map<String, String> selectKeyToWordKeyMap,
+            Map<String, Object> resultMap,
+            Map<String, StructureConfigColumnsInfo> columnsInfoMap
     ) throws SQLException {
         List<Map<String, String>> rowList = new ArrayList<Map<String, String>>();
         String entityKey = null;
         String sortSelectKey = resolveSortSelectKey(selectKeyToWordKeyMap, PERSON_FAMILY_SORT_FIELD);
-        boolean personFamilyQuery = containsPersonFamilyEntity(sqlParam);
-        boolean sortInMemory = personFamilyQuery && sortSelectKey != null;
+        boolean sortInMemory = sortSelectKey != null;
 
         while (rowSet.next()) {
             Map<String, String> rowData = new HashMap<String, String>();
@@ -128,13 +214,13 @@ public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceIm
             }
         }
 
-        if (containsPersonFamilyEntity(sqlParam) && sortInMemory) {
-            sortRowList(rowList);
+        if (sortInMemory) {
+            sortRowListAsc(rowList);
             logSortPreview(rowList);
             for (Map<String, String> rowData : rowList) {
                 rowData.remove(ROW_SORT_VALUE);
             }
-        } else if (personFamilyQuery) {
+        } else {
             LOGGER.warn("ResumeWordTemplateServiceImplEx.multiNotOne: PersonFamily detected but sortSelectKey missing, "
                     + "entityKey=" + entityKey);
         }
@@ -148,6 +234,24 @@ public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceIm
         }
     }
 
+    private void preparePersonFamilySortBeforeQuery(SqlParam param) {
+        applyPersonFamilySorter(param);
+    }
+
+    private void prepareYgzzkSortBeforeQuery(SqlParam param) {
+        applyYgzzkSorter(param);
+    }
+
+    private void applyYgzzkSorter(SqlParam param) {
+        if (!containsYgzzkEntity(param)) {
+            return;
+        }
+        String sortField = YGZZK_ENTITY.toLowerCase() + "." + YGZZK_SORT_FIELD;
+        param.setSortStr(sortField + " desc");
+        addSortFieldToSelectMapping(param.getSelectMapping(), sortField);
+        LOGGER.info("ResumeWordTemplateServiceImplEx: ygzzk sortStr=" + param.getSortStr());
+    }
+
     private void applyPersonFamilySorter(SqlParam param) {
         if (!containsPersonFamilyEntity(param)) {
             return;
@@ -157,6 +261,159 @@ public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceIm
         LOGGER.info("ResumeWordTemplateServiceImplEx: sortStr=" + param.getSortStr());
     }
 
+    private boolean containsYgzzkEntity(SqlParam param) {
+        if (containsEntity(param.getInfoCtr(), YGZZK_ENTITY)) {
+            return true;
+        }
+        Map<String, String> selectMapping = param.getSelectMapping();
+        if (selectMapping == null) {
+            return false;
+        }
+        for (String key : selectMapping.keySet()) {
+            if (isYgzzkSelectKey(key)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isYgzzkSelectKey(String selectKey) {
+        if (selectKey == null) {
+            return false;
+        }
+        return selectKey.toUpperCase().startsWith(YGZZK_ENTITY + ".");
+    }
+
+    private boolean matchesYgzzkField(String selectKey, String fieldSuffix) {
+        if (selectKey == null || fieldSuffix == null) {
+            return false;
+        }
+        if (!isYgzzkSelectKey(selectKey)) {
+            return false;
+        }
+        String upperKey = selectKey.toUpperCase();
+        String upperSuffix = fieldSuffix.toUpperCase();
+        return upperKey.equals(YGZZK_ENTITY + "." + upperSuffix)
+                || upperKey.endsWith("." + upperSuffix);
+    }
+
+    private List<String> buildYgzzkOrderedWordKeys(Map<String, String> selectKeyToWordKeyMap) {
+        List<String> orderedSelectKeys = new ArrayList<String>();
+        for (int i = 0; i < YGZZK_FIELD_ORDER.length; i++) {
+            appendMatchingSelectKeys(orderedSelectKeys, selectKeyToWordKeyMap, YGZZK_FIELD_ORDER[i]);
+        }
+        if (selectKeyToWordKeyMap != null) {
+            for (String selectKey : selectKeyToWordKeyMap.keySet()) {
+                if (isYgzzkSelectKey(selectKey) && !orderedSelectKeys.contains(selectKey)) {
+                    orderedSelectKeys.add(selectKey);
+                }
+            }
+        }
+        List<String> orderedWordKeys = new ArrayList<String>();
+        for (int i = 0; i < orderedSelectKeys.size(); i++) {
+            String wordKey = selectKeyToWordKeyMap.get(orderedSelectKeys.get(i));
+            if (wordKey != null && !orderedWordKeys.contains(wordKey)) {
+                orderedWordKeys.add(wordKey);
+            }
+        }
+        return orderedWordKeys;
+    }
+
+    private void appendMatchingSelectKeys(
+            List<String> orderedSelectKeys,
+            Map<String, String> selectKeyToWordKeyMap,
+            String fieldSuffix
+    ) {
+        if (selectKeyToWordKeyMap == null) {
+            return;
+        }
+        for (String selectKey : selectKeyToWordKeyMap.keySet()) {
+            if (matchesYgzzkField(selectKey, fieldSuffix) && !orderedSelectKeys.contains(selectKey)) {
+                orderedSelectKeys.add(selectKey);
+            }
+        }
+    }
+
+    private String mergeYgzzkSegments(List<Map<String, String>> rowList, List<String> orderedWordKeys) {
+        if (rowList == null || rowList.isEmpty() || orderedWordKeys == null || orderedWordKeys.isEmpty()) {
+            return "";
+        }
+        StringBuilder merged = new StringBuilder();
+        for (int i = 0; i < rowList.size(); i++) {
+            String segment = buildYgzzkRecordSegment(rowList.get(i), orderedWordKeys);
+            if (StringUtils.isEmpty(segment)) {
+                continue;
+            }
+            if (merged.length() > 0) {
+                merged.append(YGZZK_SEGMENT_SEPARATOR);
+            }
+            merged.append(segment);
+        }
+        return merged.toString();
+    }
+
+    private String buildYgzzkRecordSegment(Map<String, String> rowData, List<String> orderedWordKeys) {
+        if (rowData == null || orderedWordKeys == null) {
+            return "";
+        }
+        StringBuilder segment = new StringBuilder();
+        for (int i = 0; i < orderedWordKeys.size(); i++) {
+            String value = rowData.get(orderedWordKeys.get(i));
+            if (!StringUtils.isEmpty(value)) {
+                segment.append(value);
+            }
+        }
+        return segment.toString();
+    }
+
+    /** 考核时间合并展示:仅保留 yyyy,并追加「年」,如 2026年 */
+    private String formatYgzzkKaoheshij(String displayValue) {
+        if (StringUtils.isEmpty(displayValue)) {
+            return displayValue;
+        }
+        String trimmed = displayValue.trim();
+        if (trimmed.endsWith(YGZZK_YEAR_SUFFIX) && trimmed.length() >= 5) {
+            String yearPart = trimmed.substring(0, trimmed.length() - YGZZK_YEAR_SUFFIX.length());
+            if (yearPart.length() == 4 && isFourDigitYear(yearPart)) {
+                return trimmed;
+            }
+        }
+        String year = extractFourDigitYear(trimmed);
+        if (year != null) {
+            return year + YGZZK_YEAR_SUFFIX;
+        }
+        return trimmed;
+    }
+
+    private String extractFourDigitYear(String text) {
+        StringBuilder year = new StringBuilder();
+        for (int i = 0; i < text.length(); i++) {
+            char ch = text.charAt(i);
+            if (ch >= '0' && ch <= '9') {
+                year.append(ch);
+                if (year.length() == 4) {
+                    return year.toString();
+                }
+            } else if (year.length() > 0) {
+                break;
+            }
+        }
+        return year.length() == 4 ? year.toString() : null;
+    }
+
+    private boolean isFourDigitYear(String year) {
+        if (year == null || year.length() != 4) {
+            return false;
+        }
+        for (int i = 0; i < 4; i++) {
+            char ch = year.charAt(i);
+            if (ch < '0' || ch > '9') {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private boolean containsPersonFamilyEntity(SqlParam param) {
         if (containsEntity(param.getInfoCtr(), PERSON_FAMILY_ENTITY)) {
             return true;
@@ -209,21 +466,25 @@ public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceIm
     }
 
     private String resolveSortSelectKey(Map<String, String> selectKeyToWordKeyMap, String sortField) {
-        if (selectKeyToWordKeyMap == null || StringUtils.isEmpty(sortField)) {
+        return resolveSelectKeyByFieldSuffix(selectKeyToWordKeyMap, sortField);
+    }
+
+    private String resolveSelectKeyByFieldSuffix(Map<String, String> selectKeyToWordKeyMap, String fieldSuffix) {
+        if (selectKeyToWordKeyMap == null || StringUtils.isEmpty(fieldSuffix)) {
             return null;
         }
         for (String key : selectKeyToWordKeyMap.keySet()) {
-            if (key != null && key.equalsIgnoreCase(sortField)) {
+            if (key != null && key.equalsIgnoreCase(fieldSuffix)) {
                 return key;
             }
         }
-        String suffix = ".relation.number";
+        String dottedSuffix = "." + fieldSuffix;
         for (String key : selectKeyToWordKeyMap.keySet()) {
-            if (key != null && key.toUpperCase().endsWith(suffix.toUpperCase())) {
+            if (key != null && key.toUpperCase().endsWith(dottedSuffix.toUpperCase())) {
                 return key;
             }
         }
-        if (sortField != null && sortField.toUpperCase().endsWith(".NUMBER")) {
+        if (fieldSuffix.toUpperCase().endsWith(".NUMBER")) {
             for (String key : selectKeyToWordKeyMap.keySet()) {
                 if (key != null && key.toUpperCase().endsWith(".NUMBER")) {
                     return key;
@@ -258,7 +519,15 @@ public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceIm
                 + ", sortValues=[" + preview + "], count=" + rowList.size());
     }
 
-    private void sortRowList(List<Map<String, String>> rowList) {
+    private void sortRowListAsc(List<Map<String, String>> rowList) {
+        sortRowList(rowList, false);
+    }
+
+    private void sortRowListDesc(List<Map<String, String>> rowList) {
+        sortRowList(rowList, true);
+    }
+
+    private void sortRowList(List<Map<String, String>> rowList, boolean descending) {
         if (rowList == null || rowList.size() <= 1) {
             return;
         }
@@ -273,11 +542,19 @@ public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceIm
                 if (rightValue == null) {
                     rightValue = "";
                 }
-                return leftValue.compareTo(rightValue);
+                int result = leftValue.compareTo(rightValue);
+                return descending ? -result : result;
             }
         });
     }
 
+    private void limitRowList(List<Map<String, String>> rowList, int limit) {
+        if (limit <= 0 || rowList == null || rowList.size() <= limit) {
+            return;
+        }
+        rowList.subList(limit, rowList.size()).clear();
+    }
+
     private void applyPersonFamilyRowLimit(String entityKey, List<Map<String, String>> rowList) {
         if (PERSON_FAMILY_ROW_LIMIT <= 0 || rowList == null || rowList.isEmpty()) {
             return;
@@ -285,8 +562,6 @@ public class ResumeWordTemplateServiceImplEx extends ResumeWordTemplateServiceIm
         if (!isPersonFamilyEntityKey(entityKey)) {
             return;
         }
-        if (rowList.size() > PERSON_FAMILY_ROW_LIMIT) {
-            rowList.subList(PERSON_FAMILY_ROW_LIMIT, rowList.size()).clear();
-        }
+        limitRowList(rowList, PERSON_FAMILY_ROW_LIMIT);
     }
 }