package com.kingdee.eas.custom.perfweb.handler; import com.alibaba.fastjson.JSONObject; import com.kingdee.bos.BOSException; import com.kingdee.bos.Context; import com.kingdee.eas.custom.perfweb.util.ReportDateUtil; import com.kingdee.eas.custom.perfweb.util.ReportFilterUtil; import com.kingdee.eas.custom.perfweb.util.ReportOrgUtil; import com.kingdee.eas.custom.perfweb.util.ReportTurnoverUtil; import com.kingdee.shr.base.syssetting.context.SHRContext; import com.kingdee.shr.base.syssetting.exception.ShrWebBizException; import com.kingdee.shr.base.syssetting.json.GridDataEntity; import com.kingdee.shr.base.syssetting.web.handler.ListHandler; import org.apache.log4j.Logger; import org.springframework.ui.ModelMap; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.sql.SQLException; import java.util.*; /** * 类名称: ProbationTurnoverReportHandler * 功能描述: 试用期流失率报表(期间试用期人员主动/被动离职及累计离职、累计入职、累计流失率) * 创建日期: 2026-06-23 * 作 者: 青梧 * 版 本: 1.1 * * 返回字段说明: * month - 月份(合计行显示合计) * activeResign - 期间试用期人员主动离职(员工类型002) * passiveResign - 期间试用期人员被动离职 * totalResign - 期间试用期人员离职合计 * newEntry - 期间试用期人员入职人数 * ytdActiveResign - 截止目前累计主动离职 * ytdPassiveResign - 截止目前累计被动离职 * ytdTotalResign - 截止目前累计离职合计 * ytdEntry - 截止目前累计试用期入职 * ytdActiveRate - 截止目前累计主动流失率(两位小数%) * ytdPassiveRate - 截止目前累计被动流失率(两位小数%) * ytdTotalRate - 截止目前累计总流失率(两位小数%) */ public class ProbationTurnoverReportHandler extends ListHandler { private static Logger logger = Logger.getLogger(ProbationTurnoverReportHandler.class); Context ctx = SHRContext.getInstance().getContext(); /** 试用员工类型编码 */ private static final String TRIAL_EMPLOYEE_TYPE = "002"; /** * 列表查询入口:按年度逐月统计试用期人员流失,末尾追加合计行。 */ @Override protected GridDataEntity getGridRequestData(HttpServletRequest request, HttpServletResponse response, ModelMap modelMap) throws ShrWebBizException { logger.error("ProbationTurnoverReportHandler getGridRequestData------------"); GridDataEntity gridDataEntity = new GridDataEntity(); try { // 快筛(company字段可能为中心F7) + 列表筛选 String filterItem = ReportFilterUtil.getFilterItems(request); JSONObject fastFilterItemsJson = ReportFilterUtil.parseFastFilterJson(request); logger.error("filterItem: " + filterItem); logger.error("fastFilterItemsJson: " + fastFilterItemsJson); ReportOrgUtil.OrgScope scope = ReportFilterUtil.buildCenterOrgScope(ctx, fastFilterItemsJson, logger); String year = ReportFilterUtil.resolveYear(filterItem, fastFilterItemsJson, logger); logger.error("查询年度: " + year); List> monthDataList = getMonthTurnoverData(year, scope); monthDataList.add(calculateSummary(monthDataList)); gridDataEntity.setRows(monthDataList); gridDataEntity.setTotal(monthDataList.size()); gridDataEntity.setPage(1); gridDataEntity.setRecords(monthDataList.size()); logger.error("试用期流失率报表数据查询成功,总记录数: " + monthDataList.size()); } catch (Exception e) { e.fillInStackTrace(); throw new ShrWebBizException(e.getMessage()); } ReportFilterUtil.applyGridUserData(request, gridDataEntity); return gridDataEntity; } /** * 循环1~12月,逐月统计试用期离职/入职及累计指标。 */ private List> getMonthTurnoverData(String year, ReportOrgUtil.OrgScope scope) throws BOSException, SQLException { List> dataList = new ArrayList>(); int ytdActiveResign = 0; int ytdPassiveResign = 0; int ytdTotalResign = 0; int ytdEntry = 0; for (int month = 1; month <= 12; month++) { Map monthData = new LinkedHashMap(); monthData.put("month", ReportDateUtil.formatMonthLabel(year, month)); // month: 月份 String monthStart = ReportDateUtil.getMonthStart(year, month); String monthEnd = ReportDateUtil.getMonthEnd(year, month); int activeResignCount = getResignCount(monthStart, monthEnd, false, scope); monthData.put("activeResign", activeResignCount); // activeResign: 期间主动离职 int passiveResignCount = getResignCount(monthStart, monthEnd, true, scope); monthData.put("passiveResign", passiveResignCount); // passiveResign: 期间被动离职 int totalResignCount = activeResignCount + passiveResignCount; monthData.put("totalResign", totalResignCount); // totalResign: 期间离职合计 int newEntryCount = getEntryCount(monthStart, monthEnd, scope); monthData.put("newEntry", newEntryCount); // newEntry: 期间试用期入职 ytdActiveResign += activeResignCount; monthData.put("ytdActiveResign", ytdActiveResign); // ytdActiveResign: 累计主动离职 ytdPassiveResign += passiveResignCount; monthData.put("ytdPassiveResign", ytdPassiveResign); // ytdPassiveResign: 累计被动离职 ytdTotalResign += totalResignCount; monthData.put("ytdTotalResign", ytdTotalResign); // ytdTotalResign: 累计离职合计 ytdEntry += newEntryCount; monthData.put("ytdEntry", ytdEntry); // ytdEntry: 累计试用期入职 monthData.put("ytdActiveRate", ReportTurnoverUtil.formatRatioPercent( ReportTurnoverUtil.calculateRatio(ytdActiveResign, ytdEntry))); // ytdActiveRate: 累计主动流失率 monthData.put("ytdPassiveRate", ReportTurnoverUtil.formatRatioPercent( ReportTurnoverUtil.calculateRatio(ytdPassiveResign, ytdEntry))); // ytdPassiveRate: 累计被动流失率 monthData.put("ytdTotalRate", ReportTurnoverUtil.formatRatioPercent( ReportTurnoverUtil.calculateRatio(ytdTotalResign, ytdEntry))); // ytdTotalRate: 累计总流失率 dataList.add(monthData); } return dataList; } /** * 统计试用期人员离职人数。 * 口径:取离职单分录离职前员工类型 FOldEmpTypeID=002,不能用人员档案当前类型(离职后可能已变更)。 * * @param isPassive true=被动离职,false=主动离职 */ private int getResignCount(String monthStart, String monthEnd, boolean isPassive, ReportOrgUtil.OrgScope scope) throws BOSException, SQLException { StringBuilder sql = new StringBuilder(); sql.append("SELECT COUNT(*) as count "); sql.append("FROM T_HR_ResignBizBillEntry entry "); sql.append("INNER JOIN T_HR_ResignBizBill bill ON entry.FBillID = bill.FID "); sql.append("INNER JOIN T_HR_BDEmployeeType empType ON empType.FID = entry.FOLDEMPTYPEID "); sql.append("WHERE bill.FBillState = 3 "); sql.append("AND empType.FNumber = '").append(TRIAL_EMPLOYEE_TYPE).append("' "); sql.append("AND entry.FBizDate >= '").append(monthStart).append("' "); sql.append("AND entry.FBizDate <= '").append(monthEnd).append("' "); ReportTurnoverUtil.appendPassiveResignFilter(sql, isPassive); ReportOrgUtil.appendAdminOrgScopeFilter(sql, ctx, scope, "entry.FAdminOrgID"); return ReportOrgUtil.executeCountQuery(ctx, sql, "试用期人员" + (isPassive ? "被动" : "主动") + "离职人数", logger); } /** * 统计试用期人员新入职人数。 */ private int getEntryCount(String monthStart, String monthEnd, ReportOrgUtil.OrgScope scope) throws BOSException, SQLException { StringBuilder sql = new StringBuilder(); sql.append("SELECT COUNT(*) as count "); sql.append("FROM T_HR_EmpEnrollBizBillEntry entry "); sql.append("INNER JOIN T_BD_PERSON person ON entry.FPersonID = person.FID "); sql.append("INNER JOIN T_HR_BDEmployeeType empType ON empType.FID = person.FEMPLOYEETYPEID "); sql.append("WHERE empType.FNumber = '").append(TRIAL_EMPLOYEE_TYPE).append("' "); sql.append("AND entry.FBizDate >= '").append(monthStart).append("' "); sql.append("AND entry.FBizDate <= '").append(monthEnd).append("' "); ReportOrgUtil.appendAdminOrgScopeFilter(sql, ctx, scope, "entry.FAdminOrgID"); return ReportOrgUtil.executeCountQuery(ctx, sql, "试用期人员新入职人数", logger); } /** * 合计行:期间字段全年累加,累计字段取12月值并重算流失率。 */ private Map calculateSummary(List> monthDataList) { Map summary = new LinkedHashMap(); summary.put("month", "合计"); // month: 合计行 int totalActiveResign = 0; int totalPassiveResign = 0; int totalResign = 0; int totalNewEntry = 0; int totalYtdActiveResign = 0; int totalYtdPassiveResign = 0; int totalYtdTotalResign = 0; int totalYtdEntry = 0; for (Map monthData : monthDataList) { totalActiveResign += (Integer) monthData.get("activeResign"); totalPassiveResign += (Integer) monthData.get("passiveResign"); totalResign += (Integer) monthData.get("totalResign"); totalNewEntry += (Integer) monthData.get("newEntry"); } if (!monthDataList.isEmpty()) { Map lastMonth = monthDataList.get(monthDataList.size() - 1); totalYtdActiveResign = (Integer) lastMonth.get("ytdActiveResign"); totalYtdPassiveResign = (Integer) lastMonth.get("ytdPassiveResign"); totalYtdTotalResign = (Integer) lastMonth.get("ytdTotalResign"); totalYtdEntry = (Integer) lastMonth.get("ytdEntry"); } summary.put("activeResign", totalActiveResign); summary.put("passiveResign", totalPassiveResign); summary.put("totalResign", totalResign); summary.put("newEntry", totalNewEntry); summary.put("ytdActiveResign", totalYtdActiveResign); summary.put("ytdPassiveResign", totalYtdPassiveResign); summary.put("ytdTotalResign", totalYtdTotalResign); summary.put("ytdEntry", totalYtdEntry); summary.put("ytdActiveRate", ReportTurnoverUtil.formatRatioPercent( ReportTurnoverUtil.calculateRatio(totalYtdActiveResign, totalYtdEntry))); summary.put("ytdPassiveRate", ReportTurnoverUtil.formatRatioPercent( ReportTurnoverUtil.calculateRatio(totalYtdPassiveResign, totalYtdEntry))); summary.put("ytdTotalRate", ReportTurnoverUtil.formatRatioPercent( ReportTurnoverUtil.calculateRatio(totalYtdTotalResign, totalYtdEntry))); return summary; } }