yuanzhi_kuang 1 місяць тому
батько
коміт
6960f8456e

+ 101 - 0
websrc/com/kingdee/eas/hr/emp/web/handler/EmployeeListHandlerEx.java

@@ -0,0 +1,101 @@
+package com.kingdee.eas.hr.emp.web.handler;
+
+import com.kingdee.bos.Context;
+import com.kingdee.eas.basedata.person.PersonInfo;
+import com.kingdee.eas.hr.emp.WordTemplateResumeType;
+import com.kingdee.eas.hr.emp.web.handler.wordtemplate.PrintResumeWordInfo;
+import com.kingdee.eas.hr.emp.web.handler.wordtemplate.service.ResumeWordTemplateService;
+import com.kingdee.eas.hr.emp.web.handler.wordtemplate.service.impl.ResumeWordTemplateServiceImplEx;
+import com.kingdee.shr.base.syssetting.exception.ShrWebBizException;
+import org.apache.log4j.Logger;
+import org.apache.tools.zip.ZipEntry;
+import org.apache.tools.zip.ZipOutputStream;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.ByteArrayOutputStream;
+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>
+ */
+public class EmployeeListHandlerEx extends EmployeeListHandler {
+
+    private static final Logger LOGGER = Logger.getLogger(EmployeeListHandlerEx.class);
+
+    /** 履历 Word 服务,固定返回二开实现。 */
+    protected ResumeWordTemplateService getResumeWordTemplateService() {
+        return ResumeWordTemplateServiceImplEx.instance();
+    }
+
+    /**
+     * 复制标品逻辑,唯一差异:通过 getResumeWordTemplateService() 使用 ResumeWordTemplateServiceImplEx。
+     * 单人导出直接返回 doc 字节;多人导出 zip 包。
+     */
+    @Override
+    protected byte[] generateWordResumeFileBytes(
+            Context ctx,
+            HttpServletRequest request,
+            PrintResumeWordInfo printResumeWordInfo
+    ) throws ShrWebBizException {
+        String wordTemplateId = printResumeWordInfo.getWordTemplateInfo().getId().toString();
+        Map<String, PersonInfo> personIdMap = printResumeWordInfo.getPersonIdMap();
+        ResumeWordTemplateService service = getResumeWordTemplateService();
+
+        if (WordTemplateResumeType.PERSON.equals(printResumeWordInfo.getType())
+                && personIdMap != null && personIdMap.size() == 1) {
+            for (Map.Entry<String, PersonInfo> entry : personIdMap.entrySet()) {
+                return service.generateResumeWordDocument(
+                        ctx, request, entry.getKey(), wordTemplateId, "001");
+            }
+            return null;
+        }
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        ZipOutputStream zipOut = new ZipOutputStream(baos);
+        try {
+            zipOut.setEncoding("UTF-8");
+            if (personIdMap != null) {
+                for (Map.Entry<String, PersonInfo> entry : personIdMap.entrySet()) {
+                    PersonInfo personInfo = entry.getValue();
+                    String fileName = getPersonWordFileName(ctx, personInfo).toString();
+                    zipOut.putNextEntry(new ZipEntry(fileName));
+                    byte[] bytes = service.generateResumeWordDocument(
+                            ctx, request, entry.getKey(), wordTemplateId, "001");
+                    zipOut.write(bytes, 0, bytes.length);
+                }
+            }
+            zipOut.closeEntry();
+        } catch (IOException e) {
+            e.printStackTrace();
+            throw new ShrWebBizException(e.getMessage(), e);
+        } finally {
+            try {
+                zipOut.close();
+            } catch (IOException e) {
+                LOGGER.warn("io close fail!");
+            }
+        }
+        return baos.toByteArray();
+    }
+}

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

@@ -0,0 +1,292 @@
+package com.kingdee.eas.hr.emp.web.handler.wordtemplate.service.impl;
+
+import com.kingdee.bos.BOSException;
+import com.kingdee.bos.Context;
+import com.kingdee.eas.hr.base.SqlParam;
+import com.kingdee.eas.common.EASBizException;
+import com.kingdee.eas.hr.emp.web.handler.wordtemplate.service.ResumeWordTemplateService;
+import com.kingdee.jdbc.rowset.IRowSet;
+import com.kingdee.shr.baseconfig.StructureConfigColumnsInfo;
+import com.kingdee.shr.base.syssetting.exception.ShrWebBizException;
+import com.kingdee.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 员工履历 Word 导出 — 服务层二开扩展。
+ * <p>
+ * 按 familyRelation.number(与本人关系基础资料编码 FNumber)升序排序。
+ * personFamily.relation.number 在 SQL 中会映射为 FRelationID(内码),不能用于按编码排序。
+ * </p>
+ */
+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 */
+    private static final String ROW_SORT_VALUE = "__sortValue";
+
+    private static final int PERSON_FAMILY_ROW_LIMIT = 0;
+
+    private static volatile ResumeWordTemplateService INSTANCE;
+
+    public static ResumeWordTemplateService instance() {
+        if (INSTANCE == null) {
+            synchronized (ResumeWordTemplateServiceImplEx.class) {
+                if (INSTANCE == null) {
+                    INSTANCE = new ResumeWordTemplateServiceImplEx();
+                }
+            }
+        }
+        return INSTANCE;
+    }
+
+    @Override
+    protected void rowHandle(
+            Context ctx,
+            SqlParam sqlParam,
+            Map<String, String> selectKeyToWordKeyMap,
+            Map<String, Object> resultMap,
+            Map<String, StructureConfigColumnsInfo> columnsInfoMap
+    ) throws EASBizException, BOSException, ShrWebBizException, SQLException {
+        if (containsPersonFamilyEntity(sqlParam)) {
+            LOGGER.info("ResumeWordTemplateServiceImplEx.rowHandle: apply PersonFamily sort before query, field="
+                    + PERSON_FAMILY_SORT_FIELD);
+            preparePersonFamilySortBeforeQuery(sqlParam);
+        }
+        super.rowHandle(ctx, sqlParam, selectKeyToWordKeyMap, resultMap, columnsInfoMap);
+    }
+
+    @Override
+    protected void sorterHandler(Context ctx, SqlParam param) {
+        super.sorterHandler(ctx, param);
+        applyPersonFamilySorter(param);
+    }
+
+    private void preparePersonFamilySortBeforeQuery(SqlParam param) {
+        applyPersonFamilySorter(param);
+    }
+
+    @Override
+    protected void multiNotOne(
+            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;
+
+        while (rowSet.next()) {
+            Map<String, String> rowData = new HashMap<String, String>();
+            for (Map.Entry<String, String> entry : selectKeyToWordKeyMap.entrySet()) {
+                String wordKey = entry.getValue();
+                Object dbValue = rowSet.getObject(entry.getKey());
+                String displayValue = dataTypeHandle(
+                        ctx,
+                        dbValue,
+                        columnsInfoMap.get(wordKey)
+                );
+                if (!StringUtils.isEmpty(displayValue)) {
+                    rowData.put(wordKey, displayValue);
+                }
+                if (StringUtils.isEmpty(entityKey)) {
+                    int splitIndex = wordKey.indexOf('_');
+                    if (splitIndex > 0) {
+                        entityKey = wordKey.substring(0, splitIndex);
+                    }
+                }
+            }
+            if (sortInMemory) {
+                rowData.put(ROW_SORT_VALUE, resolveRowSortValue(rowSet, sortSelectKey));
+            }
+            if (rowData.size() > 0) {
+                rowList.add(rowData);
+            }
+        }
+
+        if (containsPersonFamilyEntity(sqlParam) && sortInMemory) {
+            sortRowList(rowList);
+            logSortPreview(rowList);
+            for (Map<String, String> rowData : rowList) {
+                rowData.remove(ROW_SORT_VALUE);
+            }
+        } else if (personFamilyQuery) {
+            LOGGER.warn("ResumeWordTemplateServiceImplEx.multiNotOne: PersonFamily detected but sortSelectKey missing, "
+                    + "entityKey=" + entityKey);
+        }
+
+        applyPersonFamilyRowLimit(entityKey, rowList);
+
+        if (rowList.size() > 1) {
+            resultMap.put(entityKey, rowList);
+        } else if (rowList.size() == 1) {
+            resultMap.putAll(rowList.get(0));
+        }
+    }
+
+    private void applyPersonFamilySorter(SqlParam param) {
+        if (!containsPersonFamilyEntity(param)) {
+            return;
+        }
+        param.setSortStr(PERSON_FAMILY_SORT_FIELD + " asc");
+        addSortFieldToSelectMapping(param.getSelectMapping(), PERSON_FAMILY_SORT_FIELD);
+        LOGGER.info("ResumeWordTemplateServiceImplEx: sortStr=" + param.getSortStr());
+    }
+
+    private boolean containsPersonFamilyEntity(SqlParam param) {
+        if (containsEntity(param.getInfoCtr(), PERSON_FAMILY_ENTITY)) {
+            return true;
+        }
+        Map<String, String> selectMapping = param.getSelectMapping();
+        if (selectMapping == null) {
+            return false;
+        }
+        for (String key : selectMapping.keySet()) {
+            if (isPersonFamilyFieldKey(key)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isPersonFamilyFieldKey(String key) {
+        if (key == null) {
+            return false;
+        }
+        return key.toUpperCase().startsWith(PERSON_FAMILY_ENTITY + ".");
+    }
+
+    private boolean isPersonFamilyEntityKey(String entityKey) {
+        return entityKey != null && PERSON_FAMILY_ENTITY.equalsIgnoreCase(entityKey);
+    }
+
+    private boolean containsEntity(Map infoCtr, String entityName) {
+        if (infoCtr == null || StringUtils.isEmpty(entityName)) {
+            return false;
+        }
+        if (infoCtr.containsKey(entityName) || infoCtr.containsKey(entityName.toUpperCase())) {
+            return true;
+        }
+        for (Object key : infoCtr.keySet()) {
+            if (entityName.equalsIgnoreCase(String.valueOf(key))) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void addSortFieldToSelectMapping(Map<String, String> selectMapping, String sortField) {
+        if (StringUtils.isEmpty(sortField) || selectMapping == null) {
+            return;
+        }
+        if (!containsKeyIgnoreCase(selectMapping, sortField)) {
+            selectMapping.put(sortField, sortField);
+        }
+    }
+
+    private String resolveSortSelectKey(Map<String, String> selectKeyToWordKeyMap, String sortField) {
+        if (selectKeyToWordKeyMap == null || StringUtils.isEmpty(sortField)) {
+            return null;
+        }
+        for (String key : selectKeyToWordKeyMap.keySet()) {
+            if (key != null && key.equalsIgnoreCase(sortField)) {
+                return key;
+            }
+        }
+        String suffix = ".relation.number";
+        for (String key : selectKeyToWordKeyMap.keySet()) {
+            if (key != null && key.toUpperCase().endsWith(suffix.toUpperCase())) {
+                return key;
+            }
+        }
+        if (sortField != null && sortField.toUpperCase().endsWith(".NUMBER")) {
+            for (String key : selectKeyToWordKeyMap.keySet()) {
+                if (key != null && key.toUpperCase().endsWith(".NUMBER")) {
+                    return key;
+                }
+            }
+        }
+        return null;
+    }
+
+    private String resolveRowSortValue(IRowSet rowSet, String sortSelectKey) throws SQLException {
+        if (!StringUtils.isEmpty(sortSelectKey)) {
+            Object value = rowSet.getObject(sortSelectKey);
+            if (value != null && !StringUtils.isEmpty(String.valueOf(value))) {
+                return String.valueOf(value).trim();
+            }
+        }
+        return "";
+    }
+
+    private void logSortPreview(List<Map<String, String>> rowList) {
+        if (rowList == null || rowList.isEmpty()) {
+            return;
+        }
+        StringBuilder preview = new StringBuilder();
+        for (Map<String, String> row : rowList) {
+            if (preview.length() > 0) {
+                preview.append(" | ");
+            }
+            preview.append(row.get(ROW_SORT_VALUE));
+        }
+        LOGGER.info("ResumeWordTemplateServiceImplEx.multiNotOne: sorted by " + PERSON_FAMILY_SORT_FIELD
+                + ", sortValues=[" + preview + "], count=" + rowList.size());
+    }
+
+    private void sortRowList(List<Map<String, String>> rowList) {
+        if (rowList == null || rowList.size() <= 1) {
+            return;
+        }
+        Collections.sort(rowList, new Comparator<Map<String, String>>() {
+            @Override
+            public int compare(Map<String, String> left, Map<String, String> right) {
+                String leftValue = left.get(ROW_SORT_VALUE);
+                String rightValue = right.get(ROW_SORT_VALUE);
+                if (leftValue == null) {
+                    leftValue = "";
+                }
+                if (rightValue == null) {
+                    rightValue = "";
+                }
+                return leftValue.compareTo(rightValue);
+            }
+        });
+    }
+
+    private void applyPersonFamilyRowLimit(String entityKey, List<Map<String, String>> rowList) {
+        if (PERSON_FAMILY_ROW_LIMIT <= 0 || rowList == null || rowList.isEmpty()) {
+            return;
+        }
+        if (!isPersonFamilyEntityKey(entityKey)) {
+            return;
+        }
+        if (rowList.size() > PERSON_FAMILY_ROW_LIMIT) {
+            rowList.subList(PERSON_FAMILY_ROW_LIMIT, rowList.size()).clear();
+        }
+    }
+}