KeyTalentTurnoverReportHandler.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. package com.kingdee.eas.custom.perfweb.handler;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.kingdee.bos.BOSException;
  4. import com.kingdee.bos.Context;
  5. import com.kingdee.eas.custom.perfweb.util.ReportDateUtil;
  6. import com.kingdee.eas.custom.perfweb.util.ReportFilterUtil;
  7. import com.kingdee.eas.custom.perfweb.util.ReportOrgUtil;
  8. import com.kingdee.eas.custom.perfweb.util.ReportTurnoverUtil;
  9. import com.kingdee.shr.base.syssetting.context.SHRContext;
  10. import com.kingdee.shr.base.syssetting.exception.ShrWebBizException;
  11. import com.kingdee.shr.base.syssetting.json.GridDataEntity;
  12. import com.kingdee.shr.base.syssetting.web.handler.ListHandler;
  13. import org.apache.log4j.Logger;
  14. import org.springframework.ui.ModelMap;
  15. import javax.servlet.http.HttpServletRequest;
  16. import javax.servlet.http.HttpServletResponse;
  17. import java.sql.SQLException;
  18. import java.util.*;
  19. /**
  20. * 类名称: KeyTalentTurnoverReportHandler
  21. * 功能描述: 关键人才流失率报表(期初/主动离职/被动离职/离职合计/新增/期末/累计主动流失率)
  22. * 创建日期: 2026-06-23
  23. * 作 者: 青梧
  24. * 版 本: 1.1
  25. *
  26. * 期初/期末口径(B方案):
  27. * - 1月期初:1月1日关键人才快照
  28. * - 期末:期初 - 离职 + 新增
  29. * - 2~12月期初:上月推算期末(链式)
  30. *
  31. * 返回字段说明:
  32. * month - 月份(合计行显示合计)
  33. * beginKeyTalent - 期初关键人才人数
  34. * activeResign - 期间关键人才主动离职
  35. * passiveResign - 期间关键人才被动离职
  36. * totalResign - 期间离职合计
  37. * newKeyTalent - 期间新增关键人才
  38. * endKeyTalent - 期末关键人才(链式推算)
  39. * ytdActiveResign - 累计主动离职
  40. * ytdNewKeyTalent - 累计新增关键人才(截止目前)
  41. * ytdActiveRate - 累计主动流失率(保留两位小数%)
  42. */
  43. public class KeyTalentTurnoverReportHandler extends ListHandler {
  44. private static Logger logger = Logger.getLogger(KeyTalentTurnoverReportHandler.class);
  45. Context ctx = SHRContext.getInstance().getContext();
  46. /** 关键人才标识:CFGjrc=1 */
  47. private static final String KEY_TALENT_FLAG = "1";
  48. /** 排除的员工类型FID(非在职) */
  49. private static final String EXCLUDED_EMPLOYEE_TYPE_ID = "00000000-0000-0000-0000-000000000007A29E85B3";
  50. @Override
  51. protected GridDataEntity getGridRequestData(HttpServletRequest request, HttpServletResponse response, ModelMap modelMap) throws ShrWebBizException {
  52. logger.error("KeyTalentTurnoverReportHandler getGridRequestData------------");
  53. GridDataEntity gridDataEntity = new GridDataEntity();
  54. try {
  55. String filterItem = ReportFilterUtil.getFilterItems(request);
  56. JSONObject fastFilterItemsJson = ReportFilterUtil.parseFastFilterJson(request);
  57. logger.error("filterItem: " + filterItem);
  58. logger.error("fastFilterItemsJson: " + fastFilterItemsJson);
  59. ReportOrgUtil.OrgScope scope = ReportFilterUtil.buildCenterOrgScope(ctx, fastFilterItemsJson, logger);
  60. String year = ReportFilterUtil.resolveYear(filterItem, fastFilterItemsJson, logger);
  61. logger.error("查询年度: " + year);
  62. List<Map<String, Object>> monthDataList = getMonthTurnoverData(year, scope);
  63. monthDataList.add(calculateSummary(monthDataList, year));
  64. gridDataEntity.setRows(monthDataList);
  65. gridDataEntity.setTotal(monthDataList.size());
  66. gridDataEntity.setPage(1);
  67. gridDataEntity.setRecords(monthDataList.size());
  68. logger.error("关键人才流失率报表数据查询成功,总记录数: " + monthDataList.size());
  69. } catch (Exception e) {
  70. e.fillInStackTrace();
  71. throw new ShrWebBizException(e.getMessage());
  72. }
  73. ReportFilterUtil.applyGridUserData(request, gridDataEntity);
  74. return gridDataEntity;
  75. }
  76. /**
  77. * 循环1~12月,逐月统计关键人才期初/离职/新增/期末及累计指标(B方案链式推算)。
  78. */
  79. private List<Map<String, Object>> getMonthTurnoverData(String year, ReportOrgUtil.OrgScope scope) throws BOSException, SQLException {
  80. List<Map<String, Object>> dataList = new ArrayList<Map<String, Object>>();
  81. int yearBeginKeyTalent = getBeginKeyTalentCount(year + "-01-01", scope);
  82. int ytdActiveResign = 0;
  83. int ytdNewKeyTalent = 0;
  84. int previousEndKeyTalent = 0;
  85. for (int month = 1; month <= 12; month++) {
  86. Map<String, Object> monthData = new LinkedHashMap<String, Object>();
  87. monthData.put("month", ReportDateUtil.formatMonthLabel(year, month));
  88. String monthStart = ReportDateUtil.getMonthStart(year, month);
  89. String monthEnd = ReportDateUtil.getMonthEnd(year, month);
  90. int beginCount;
  91. if (month == 1) {
  92. beginCount = getBeginKeyTalentCount(monthStart, scope);
  93. } else {
  94. beginCount = previousEndKeyTalent;
  95. }
  96. monthData.put("beginKeyTalent", beginCount);
  97. int activeResignCount = getResignCount(monthStart, monthEnd, false, scope);
  98. monthData.put("activeResign", activeResignCount);
  99. int passiveResignCount = getResignCount(monthStart, monthEnd, true, scope);
  100. monthData.put("passiveResign", passiveResignCount);
  101. int totalResignCount = activeResignCount + passiveResignCount;
  102. monthData.put("totalResign", totalResignCount);
  103. int newKeyTalentCount = getNewKeyTalentCount(monthStart, monthEnd, scope);
  104. monthData.put("newKeyTalent", newKeyTalentCount);
  105. int endKeyTalentCount = ReportTurnoverUtil.calculateDerivedEndOnDuty(beginCount, newKeyTalentCount, totalResignCount);
  106. monthData.put("endKeyTalent", endKeyTalentCount);
  107. previousEndKeyTalent = endKeyTalentCount;
  108. ytdActiveResign += activeResignCount;
  109. ytdNewKeyTalent += newKeyTalentCount;
  110. monthData.put("ytdActiveResign", ytdActiveResign);
  111. monthData.put("ytdNewKeyTalent", ytdNewKeyTalent);
  112. monthData.put("ytdActiveRate", ReportTurnoverUtil.formatRatioPercent(
  113. ReportTurnoverUtil.calculateRatio(ytdActiveResign, yearBeginKeyTalent + ytdNewKeyTalent)));
  114. dataList.add(monthData);
  115. }
  116. return dataList;
  117. }
  118. /**
  119. * 统计期初关键人才人数(CFGjrc=1,仅用于1月期初快照)。
  120. */
  121. private int getBeginKeyTalentCount(String monthStart, ReportOrgUtil.OrgScope scope) throws BOSException, SQLException {
  122. StringBuilder sql = new StringBuilder();
  123. sql.append("SELECT COUNT(DISTINCT person.FID) as count ");
  124. sql.append("FROM T_BD_PERSON person ");
  125. sql.append("LEFT JOIN T_HR_PersonPosition personPos ON person.FID = personPos.FPERSONID ");
  126. sql.append("WHERE person.CFGjrc = '").append(KEY_TALENT_FLAG).append("' ");
  127. sql.append("AND person.FEmployeeTypeID <> '").append(EXCLUDED_EMPLOYEE_TYPE_ID).append("' ");
  128. sql.append("AND (personPos.FLeftDate IS NULL OR personPos.FLeftDate >= '").append(monthStart).append("') ");
  129. sql.append("AND person.FHireDate IS NOT NULL ");
  130. sql.append("AND person.FHireDate <= '").append(monthStart).append("' ");
  131. ReportOrgUtil.appendPersonDepScopeFilter(sql, ctx, scope);
  132. return ReportOrgUtil.executeCountQuery(ctx, sql, "期初关键人才人数", logger);
  133. }
  134. private int getResignCount(String monthStart, String monthEnd, boolean isPassive, ReportOrgUtil.OrgScope scope) throws BOSException, SQLException {
  135. StringBuilder sql = new StringBuilder();
  136. sql.append("SELECT COUNT(*) as count ");
  137. sql.append("FROM T_HR_ResignBizBillEntry entry ");
  138. sql.append("INNER JOIN T_HR_ResignBizBill bill ON entry.FBillID = bill.FID ");
  139. sql.append("INNER JOIN T_BD_PERSON person ON entry.FPersonID = person.FID ");
  140. sql.append("WHERE bill.FBillState = 3 ");
  141. sql.append("AND person.CFGjrc = '").append(KEY_TALENT_FLAG).append("' ");
  142. sql.append("AND entry.FBizDate >= '").append(monthStart).append("' ");
  143. sql.append("AND entry.FBizDate <= '").append(monthEnd).append("' ");
  144. ReportTurnoverUtil.appendPassiveResignFilter(sql, isPassive);
  145. ReportOrgUtil.appendAdminOrgScopeFilter(sql, ctx, scope, "entry.FAdminOrgID");
  146. return ReportOrgUtil.executeCountQuery(ctx, sql,
  147. "关键人才" + (isPassive ? "被动" : "主动") + "离职人数", logger);
  148. }
  149. /**
  150. * 统计新增关键人才人数(入职且CFGjrc=1)。
  151. */
  152. private int getNewKeyTalentCount(String monthStart, String monthEnd, ReportOrgUtil.OrgScope scope) throws BOSException, SQLException {
  153. StringBuilder sql = new StringBuilder();
  154. sql.append("SELECT COUNT(*) as count ");
  155. sql.append("FROM T_HR_EmpEnrollBizBillEntry entry ");
  156. sql.append("INNER JOIN T_BD_PERSON person ON entry.FPersonID = person.FID ");
  157. sql.append("WHERE person.CFGjrc = '").append(KEY_TALENT_FLAG).append("' ");
  158. sql.append("AND entry.FBizDate >= '").append(monthStart).append("' ");
  159. sql.append("AND entry.FBizDate <= '").append(monthEnd).append("' ");
  160. ReportOrgUtil.appendAdminOrgScopeFilter(sql, ctx, scope, "entry.FAdminOrgID");
  161. return ReportOrgUtil.executeCountQuery(ctx, sql, "新增关键人才人数", logger);
  162. }
  163. /**
  164. * 合计行:1~当前月累计,期初取1月、期末取当前月链式推算结果。
  165. */
  166. private Map<String, Object> calculateSummary(List<Map<String, Object>> monthDataList, String year) {
  167. Map<String, Object> summary = new LinkedHashMap<String, Object>();
  168. summary.put("month", "合计");
  169. int currentMonth = resolveSummaryMonth(year);
  170. int yearBeginKeyTalent = 0;
  171. int currentMonthEndKeyTalent = 0;
  172. int totalActiveResign = 0;
  173. int totalPassiveResign = 0;
  174. int totalResign = 0;
  175. int totalNewKeyTalent = 0;
  176. if (!monthDataList.isEmpty()) {
  177. yearBeginKeyTalent = (Integer) monthDataList.get(0).get("beginKeyTalent");
  178. }
  179. for (int i = 0; i < monthDataList.size() && i < currentMonth; i++) {
  180. Map<String, Object> monthData = monthDataList.get(i);
  181. totalActiveResign += (Integer) monthData.get("activeResign");
  182. totalPassiveResign += (Integer) monthData.get("passiveResign");
  183. totalResign += (Integer) monthData.get("totalResign");
  184. totalNewKeyTalent += (Integer) monthData.get("newKeyTalent");
  185. currentMonthEndKeyTalent = (Integer) monthData.get("endKeyTalent");
  186. }
  187. summary.put("beginKeyTalent", yearBeginKeyTalent);
  188. summary.put("activeResign", totalActiveResign);
  189. summary.put("passiveResign", totalPassiveResign);
  190. summary.put("totalResign", totalResign);
  191. summary.put("newKeyTalent", totalNewKeyTalent);
  192. summary.put("endKeyTalent", currentMonthEndKeyTalent);
  193. summary.put("ytdActiveResign", totalActiveResign);
  194. summary.put("ytdNewKeyTalent", totalNewKeyTalent);
  195. summary.put("ytdActiveRate", ReportTurnoverUtil.formatRatioPercent(
  196. ReportTurnoverUtil.calculateRatio(totalActiveResign, yearBeginKeyTalent + totalNewKeyTalent)));
  197. return summary;
  198. }
  199. /**
  200. * 合计统计截止月:查询年为今年取系统当前月,往年取12月,未来年取1月。
  201. */
  202. private int resolveSummaryMonth(String year) {
  203. int queryYear = Integer.parseInt(year);
  204. Calendar calendar = Calendar.getInstance();
  205. int currentYear = calendar.get(Calendar.YEAR);
  206. if (queryYear < currentYear) {
  207. return 12;
  208. }
  209. if (queryYear > currentYear) {
  210. return 1;
  211. }
  212. return calendar.get(Calendar.MONTH) + 1;
  213. }
  214. }