| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567 |
- 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.bos.ctrl.swing.StringUtils;
- import com.kingdee.jdbc.rowset.IRowSet;
- 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.util.app.DbUtil;
- 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.math.BigDecimal;
- import java.math.RoundingMode;
- import java.sql.SQLException;
- import java.sql.Timestamp;
- import java.text.SimpleDateFormat;
- import java.util.*;
- /**
- * 类名称: IndirectPersonnelOvertimeReportHandler
- * 功能描述: 间接人员加班工时报表(打卡计算平时/周末加班工时)
- * 创建日期: 2026-06-23
- * 作 者: 青梧
- * 版 本: 1.0
- * <p>
- * 返回字段说明:
- * month - 月份(合计行显示合计)
- * personNumber - 员工工号
- * centerName - 所属中心名称(沿FParentID向上找,FLevel=2,列表列名沿用centerName)
- * deptName - 所属部门名称
- * personName - 员工姓名
- * weekdayOtHours - 平时加班工时(最晚打卡-排班下班,0.5h取整)
- * weekendOtHours - 周末加班工时(最晚-最早,0.5h取整)
- * totalOtHours - 加班工时合计
- * _sortDept - 内部排序字段(部门)
- * _sortName - 内部排序字段(姓名)
- * <p>
- * 核心数据结构说明:
- * personMap : Map<personId, PersonRow> 间接人员基础信息
- * punchMap : Map<personId, Map<dayKey, List<打卡时间>>> 当月全员打卡,按人+日分组
- * offDutyMap : Map<personId, Map<dayKey, 排班下班时间>> 当月全员排班下班时间
- * dayKey : 日期字符串 yyyy-MM-dd,作为按天计算的键
- */
- public class IndirectPersonnelOvertimeReportHandler extends ListHandler {
- private static Logger logger = Logger.getLogger(IndirectPersonnelOvertimeReportHandler.class);
- Context ctx = SHRContext.getInstance().getContext();
- /**
- * 间接人员类别:person.CFRylb=2
- */
- private static final int INDIRECT_RYLB = 2;
- /**
- * 周末最早打卡早于13:15时扣除的午休时长(小时)
- */
- private static final double LUNCH_DEDUCT_HOURS = 1.5D;
- /**
- * 午休扣除阈值:13:15(分钟数)
- */
- private static final int AFTERNOON_THRESHOLD_MINUTES = 13 * 60 + 15;
- /**
- * 中心组织层级(T_ORG_Admin.FLevel)。
- * TODO(待业务确认): 当前按2级作为中心,若组织架构调整需同步校验。
- */
- private static final int CENTER_ORG_LEVEL = 2;
- /**
- * 列表查询入口:按日期范围/组织/人员筛选间接人员,计算加班工时并追加合计行。
- */
- @Override
- protected GridDataEntity getGridRequestData(HttpServletRequest request, HttpServletResponse response, ModelMap modelMap) throws ShrWebBizException {
- logger.error("IndirectPersonnelOvertimeReportHandler getGridRequestData------------");
- GridDataEntity gridDataEntity = new GridDataEntity();
- try {
- // 快筛: 行政组织adminOrg + 人员person + 日期month
- String filterItem = ReportFilterUtil.getFilterItems(request);
- JSONObject fastFilterItemsJson = ReportFilterUtil.parseFastFilterJson(request);
- logger.error("filterItem: " + filterItem);
- logger.error("fastFilterItemsJson: " + fastFilterItemsJson);
- Map<String, String> paramMap = ReportFilterUtil.parseAdminOrgParams(fastFilterItemsJson, logger);
- String adminOrg = paramMap.get("adminOrg");
- List<String> personIds = ReportFilterUtil.parseF7IdList(fastFilterItemsJson, "person");
- // dateRange: 开始/结束日期,优先快筛字段(name=month),未传则默认当月
- ReportFilterUtil.DateRange dateRange = ReportFilterUtil.resolveQueryDateRange(filterItem, fastFilterItemsJson, "month", logger);
- logger.error("查询日期: " + dateRange.getStartDate() + " ~ " + dateRange.getEndDate() + ", 组织: " + adminOrg + ", 人员: " + personIds);
- // personMap: key=人员FID, value=人员基础信息(工号/姓名/部门/中心)
- Map<String, PersonRow> personMap = loadIndirectPersons(adminOrg, personIds);
- // dataList: 最终返回给前端的报表行列表
- List<Map<String, Object>> dataList = buildReportRows(dateRange, personMap, adminOrg);
- dataList.sort(new RowComparator());
- dataList.add(calculateSummary(dataList));
- gridDataEntity.setRows(dataList);
- gridDataEntity.setTotal(dataList.size());
- gridDataEntity.setPage(1);
- gridDataEntity.setRecords(dataList.size());
- logger.error("间接人员加班报表数据查询成功,总记录数: " + dataList.size());
- } catch (Exception e) {
- e.fillInStackTrace();
- throw new ShrWebBizException(e.getMessage());
- }
- ReportFilterUtil.applyGridUserData(request, gridDataEntity);
- return gridDataEntity;
- }
- /**
- * 按日期范围遍历间接人员,计算加班工时。
- * 平时加班=最晚打卡-排班下班;周末加班=最晚-最早,若最早早于13:15扣1.5h,均按0.5h取整。
- * @param dateRange 查询日期范围(yyyy-MM-dd),快筛字段(name=month)
- */
- private List<Map<String, Object>> buildReportRows(ReportFilterUtil.DateRange dateRange, Map<String, PersonRow> personMap, String adminOrg) throws BOSException, SQLException {
- List<Map<String, Object>> dataList = new ArrayList<Map<String, Object>>();
- if (personMap.isEmpty() || dateRange == null) {
- return dataList;
- }
- String queryStart = dateRange.getStartDate();
- String queryEnd = dateRange.getEndDate();
- // punchMap: 人员ID -> (日期 -> 当天全部打卡时间列表,已排序)
- Map<String, Map<String, List<Timestamp>>> punchMap = loadPunchRecords(queryStart, queryEnd, adminOrg, personMap.keySet());
- // offDutyMap: 人员ID -> (日期 -> 排班下班时间)
- Map<String, Map<String, Timestamp>> offDutyMap = loadScheduleOffDutyTimes(queryStart, queryEnd, adminOrg, personMap.keySet());
- // ---------- 遍历间接人员 ----------
- for (PersonRow person : personMap.values()) {
- // dayPunchMap: 当前人员 日期->打卡列表
- Map<String, List<Timestamp>> dayPunchMap = punchMap.get(person.personId);
- // dayOffDutyMap: 当前人员 日期->下班时间
- Map<String, Timestamp> dayOffDutyMap = offDutyMap.get(person.personId);
- if (dayPunchMap == null || dayPunchMap.isEmpty()) {
- continue;
- }
- // monthHoursMap: yyyy-MM -> 当月加班工时(平时+周末)
- Map<String, MonthOtHours> monthHoursMap = new LinkedHashMap<String, MonthOtHours>();
- // ---------- 按天遍历打卡 ----------
- for (Map.Entry<String, List<Timestamp>> entry : dayPunchMap.entrySet()) {
- // dayKey: 日期字符串 yyyy-MM-dd
- String dayKey = entry.getKey();
- if (dayKey.compareTo(queryStart) < 0 || dayKey.compareTo(queryEnd) > 0) {
- continue;
- }
- // punches: 当天全部打卡(已排序,index0=最早,尾=最晚)
- List<Timestamp> punches = entry.getValue();
- if (punches == null || punches.size() < 2) {
- continue;
- }
- Timestamp earliest = punches.get(0);
- Timestamp latest = punches.get(punches.size() - 1);
- if (earliest == null || latest == null || !latest.after(earliest)) {
- continue;
- }
- // yearMonthKey: 所属月份 yyyy-MM,用于按月汇总
- String yearMonthKey = dayKey.substring(0, 7);
- MonthOtHours monthHours = monthHoursMap.get(yearMonthKey);
- if (monthHours == null) {
- monthHours = new MonthOtHours();
- monthHoursMap.put(yearMonthKey, monthHours);
- }
- if (isWeekend(dayKey)) {
- double rawHours = hoursBetween(earliest, latest);
- if (isBeforeAfternoonThreshold(earliest)) {
- rawHours -= LUNCH_DEDUCT_HOURS;
- }
- monthHours.weekendHours = monthHours.weekendHours.add(roundHalfHour(rawHours));
- } else {
- Timestamp offDuty = dayOffDutyMap != null ? dayOffDutyMap.get(dayKey) : null;
- if (offDuty == null) {
- continue;
- }
- double rawHours = hoursBetween(offDuty, latest);
- monthHours.weekdayHours = monthHours.weekdayHours.add(roundHalfHour(rawHours));
- }
- }
- for (Map.Entry<String, MonthOtHours> monthEntry : monthHoursMap.entrySet()) {
- MonthOtHours hours = monthEntry.getValue();
- BigDecimal totalHours = hours.weekdayHours.add(hours.weekendHours);
- if (totalHours.compareTo(BigDecimal.ZERO) <= 0) {
- continue;
- }
- Map<String, Object> row = new LinkedHashMap<String, Object>();
- row.put("month", ReportDateUtil.formatMonthLabelFromDayKey(monthEntry.getKey() + "-01")); // month: 月份
- row.put("personNumber", person.personNumber); // personNumber: 工号
- row.put("centerName", person.centerName); // centerName: 中心名称(列表列名沿用centerName)
- row.put("deptName", person.deptName); // deptName: 部门
- row.put("personName", person.personName); // personName: 姓名
- row.put("weekdayOtHours", hours.weekdayHours); // weekdayOtHours: 平时加班
- row.put("weekendOtHours", hours.weekendHours); // weekendOtHours: 周末加班
- row.put("totalOtHours", totalHours); // totalOtHours: 加班合计
- row.put("_sortDept", person.deptName == null ? "" : person.deptName); // _sortDept: 排序用部门
- row.put("_sortName", person.personName == null ? "" : person.personName); // _sortName: 排序用姓名
- dataList.add(row);
- }
- }
- return dataList;
- }
- /**
- * 加载间接人员列表(CFRylb=2, 未离职),并解析所属中心名称。
- *
- * @return personMap: key=人员FID, value=PersonRow
- */
- private Map<String, PersonRow> loadIndirectPersons(String adminOrg, List<String> personIds) throws BOSException, SQLException {
- Map<String, PersonRow> personMap = new LinkedHashMap<String, PersonRow>();
- // centerNameCache: key=部门FID, value=所属中心名称(避免重复查询)
- Map<String, String> centerNameCache = new HashMap<String, String>();
- StringBuilder sql = new StringBuilder();
- sql.append("SELECT person.FID AS personId, person.FNUMBER AS personNumber, person.FNAME_L2 AS personName, ");
- sql.append("dept.FID AS deptId, dept.FNAME_L2 AS deptName, dept.FLONGNUMBER AS deptLongNumber ");
- sql.append("FROM T_BD_PERSON person ");
- sql.append("LEFT JOIN T_HR_PersonPosition personPos ON person.FID = personPos.FPERSONID ");
- sql.append("LEFT JOIN T_ORG_Admin dept ON dept.FID = personPos.FPersonDep ");
- sql.append("WHERE person.CFRylb = ").append(INDIRECT_RYLB).append(" ");
- sql.append("AND personPos.FLeftDate IS NULL ");
- ReportFilterUtil.appendSqlInClause(sql, "person.FID", personIds);
- ReportOrgUtil.appendOrgFilter(sql, ctx, adminOrg, "personPos.FPersonDep");
- sql.append("ORDER BY person.FNUMBER ");
- logger.error("间接人员SQL: " + sql);
- IRowSet rs = DbUtil.executeQuery(ctx, sql.toString());
- while (rs.next()) {
- // 人员ID
- String pid = rs.getString("personId");
- if (StringUtils.isEmpty(pid) || personMap.containsKey(pid)) {
- continue;
- }
- PersonRow row = new PersonRow();
- row.personId = pid;
- // 工号
- row.personNumber = rs.getString("personNumber");
- // 姓名
- row.personName = rs.getString("personName");
- // 部门名称
- row.deptName = rs.getString("deptName");
- // 部门FID,用于向上查找中心名称
- String deptId = rs.getString("deptId");
- row.centerName = resolveCenterName(deptId, centerNameCache);
- personMap.put(pid, row);
- }
- return personMap;
- }
- /**
- * 沿组织树向上查找中心名称(FParentID 递归,FLevel=CENTER_ORG_LEVEL)。
- * 列表列名 centerName 沿用中心报表字段名。
- * TODO(待业务确认): 若中心层级不是 CENTER_ORG_LEVEL,需同步调整。
- *
- * @param deptId 人员所属部门 FID
- * @param cache 部门FID->中心名称缓存,避免重复查询
- */
- private String resolveCenterName(String deptId, Map<String, String> cache) throws BOSException, SQLException {
- if (StringUtils.isEmpty(deptId)) {
- return "";
- }
- if (cache.containsKey(deptId)) {
- return cache.get(deptId);
- }
- String currentId = deptId;
- // 所属中心名称(列表列名centerName)
- String centerName = "";
- for (int depth = 0; depth < 20; depth++) {
- StringBuilder sql = new StringBuilder();
- sql.append("SELECT FNAME_L2 AS orgName, FLevel AS orgLevel, FParentID AS parentId ");
- sql.append("FROM T_ORG_Admin WHERE FID = ? AND FIsSealUp = 0 ");
- IRowSet rs = DbUtil.executeQuery(ctx, sql.toString(), new Object[]{currentId});
- if (!rs.next()) {
- break;
- }
- int orgLevel = rs.getInt("orgLevel");
- String orgName = rs.getString("orgName");
- // 找到中心层级(CENTER_ORG_LEVEL)即停止
- if (orgLevel == CENTER_ORG_LEVEL) {
- centerName = orgName == null ? "" : orgName;
- break;
- }
- String parentId = rs.getString("parentId");
- if (StringUtils.isEmpty(parentId)) {
- break;
- }
- currentId = parentId;
- }
- cache.put(deptId, centerName);
- return centerName;
- }
- /**
- * 加载打卡记录,按人员+日期分组。
- *
- * @return Map结构: personId -> (dayKey -> 当天打卡时间列表,已排序)
- */
- private Map<String, Map<String, List<Timestamp>>> loadPunchRecords(String monthStart, String monthEnd, String adminOrg, Set<String> personIds) throws BOSException, SQLException {
- // result: 外层key=personId, 内层key=dayKey(yyyy-MM-dd), value=打卡时间列表
- Map<String, Map<String, List<Timestamp>>> result = new HashMap<String, Map<String, List<Timestamp>>>();
- StringBuilder sql = new StringBuilder();
- sql.append("SELECT punch.FProposerID AS personId, punch.FPunchCardDate AS punchDate, punch.FPunchCardTime AS punchTime ");
- sql.append("FROM T_HR_ATS_PunchCardRecord punch ");
- sql.append("INNER JOIN T_BD_PERSON person ON person.FID = punch.FProposerID ");
- sql.append("LEFT JOIN T_HR_PersonPosition personPos ON person.FID = personPos.FPERSONID ");
- sql.append("WHERE person.CFRylb = ").append(INDIRECT_RYLB).append(" ");
- sql.append("AND punch.FPunchCardDate >= '").append(monthStart).append("' ");
- sql.append("AND punch.FPunchCardDate <= '").append(monthEnd).append("' ");
- sql.append("AND punch.FPunchCardTime IS NOT NULL ");
- ReportOrgUtil.appendOrgFilter(sql, ctx, adminOrg, "personPos.FPersonDep");
- sql.append("ORDER BY punch.FProposerID, punch.FPunchCardDate, punch.FPunchCardTime ");
- logger.error("打卡记录SQL[" + monthStart + "~" + monthEnd + "]: " + sql);
- IRowSet rs = DbUtil.executeQuery(ctx, sql.toString());
- SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
- while (rs.next()) {
- // 人员ID
- String personId = rs.getString("personId");
- if (personIds != null && !personIds.isEmpty() && !personIds.contains(personId)) {
- continue;
- }
- // 打卡日期
- Date punchDate = rs.getDate("punchDate");
- // 打卡时间
- Timestamp punchTime = rs.getTimestamp("punchTime");
- if (punchDate == null || punchTime == null) {
- continue;
- }
- // dayKey: 日期字符串,作为内层Map的key
- String dayKey = dayFormat.format(punchDate);
- // dayMap: 当前人员 日期->打卡列表 的内层Map
- Map<String, List<Timestamp>> dayMap = result.get(personId);
- if (dayMap == null) {
- dayMap = new HashMap<String, List<Timestamp>>();
- result.put(personId, dayMap);
- }
- // punches: 当天打卡时间列表
- List<Timestamp> punches = dayMap.get(dayKey);
- if (punches == null) {
- punches = new ArrayList<Timestamp>();
- dayMap.put(dayKey, punches);
- }
- punches.add(punchTime);
- }
- return result;
- }
- /**
- * 加载排班下班时间(FNextDateTime),按人员+日期分组。
- * 排班明细(ScheduleShiftItem)通过 FGroupID 关联主表(ScheduleShift.FID)。
- *
- * @return Map结构: personId -> (dayKey -> 排班下班时间)
- */
- private Map<String, Map<String, Timestamp>> loadScheduleOffDutyTimes(String monthStart, String monthEnd, String adminOrg, Set<String> personIds) throws BOSException, SQLException {
- // result: 外层key=personId, 内层key=dayKey(yyyy-MM-dd), value=下班时间
- Map<String, Map<String, Timestamp>> result = new HashMap<String, Map<String, Timestamp>>();
- StringBuilder sql = new StringBuilder();
- sql.append("SELECT sch.FProposerID AS personId, sch.FAttendDate AS attendDate, MAX(item.FNextDateTime) AS offDutyTime ");
- sql.append("FROM T_HR_ATS_ScheduleShift sch ");
- // 班次明细: item.FGroupID -> sch.FID
- sql.append("INNER JOIN T_HR_ATS_ScheduleShiftItem item ON item.FGroupID = sch.FID ");
- sql.append("INNER JOIN T_BD_PERSON person ON person.FID = sch.FProposerID ");
- sql.append("LEFT JOIN T_HR_PersonPosition personPos ON person.FID = personPos.FPERSONID ");
- sql.append("WHERE person.CFRylb = ").append(INDIRECT_RYLB).append(" ");
- sql.append("AND sch.FAttendDate >= '").append(monthStart).append("' ");
- sql.append("AND sch.FAttendDate <= '").append(monthEnd).append("' ");
- sql.append("AND item.FNextDateTime IS NOT NULL ");
- ReportOrgUtil.appendOrgFilter(sql, ctx, adminOrg, "personPos.FPersonDep");
- sql.append("GROUP BY sch.FProposerID, sch.FAttendDate ");
- logger.error("排班下班SQL[" + monthStart + "~" + monthEnd + "]: " + sql);
- IRowSet rs = DbUtil.executeQuery(ctx, sql.toString());
- SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
- while (rs.next()) {
- // 人员ID
- String personId = rs.getString("personId");
- if (personIds != null && !personIds.isEmpty() && !personIds.contains(personId)) {
- continue;
- }
- // 出勤日期
- Date attendDate = rs.getDate("attendDate");
- // 排班下班时间(FNextDateTime)
- Timestamp offDutyTime = rs.getTimestamp("offDutyTime");
- if (attendDate == null || offDutyTime == null) {
- continue;
- }
- // dayKey: 日期字符串
- String dayKey = dayFormat.format(attendDate);
- // dayMap: 当前人员 日期->下班时间 的内层Map
- Map<String, Timestamp> dayMap = result.get(personId);
- if (dayMap == null) {
- dayMap = new HashMap<String, Timestamp>();
- result.put(personId, dayMap);
- }
- // 同一天取最晚的下班时间
- Timestamp existing = dayMap.get(dayKey);
- if (existing == null || offDutyTime.after(existing)) {
- dayMap.put(dayKey, offDutyTime);
- }
- }
- return result;
- }
- /**
- * 判断是否为周末(周六/周日),使用Calendar实现。
- */
- private boolean isWeekend(String dayKey) {
- try {
- SimpleDateFormat dayFormat = new SimpleDateFormat("yyyy-MM-dd");
- Date date = dayFormat.parse(dayKey);
- Calendar calendar = Calendar.getInstance();
- calendar.setTime(date);
- int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
- return dayOfWeek == Calendar.SATURDAY || dayOfWeek == Calendar.SUNDAY;
- } catch (Exception e) {
- logger.error("日期解析失败: " + dayKey, e);
- return false;
- }
- }
- /**
- * 判断打卡时间是否早于13:15
- */
- private boolean isBeforeAfternoonThreshold(Timestamp punchTime) {
- Calendar calendar = Calendar.getInstance();
- calendar.setTime(punchTime);
- int minutes = calendar.get(Calendar.HOUR_OF_DAY) * 60 + calendar.get(Calendar.MINUTE);
- return minutes < AFTERNOON_THRESHOLD_MINUTES;
- }
- /**
- * 计算两个时间戳之间的小时差
- */
- private double hoursBetween(Timestamp start, Timestamp end) {
- long millis = end.getTime() - start.getTime();
- if (millis <= 0) {
- return 0D;
- }
- return millis / (1000D * 60D * 60D);
- }
- /**
- * 按0.5小时向下取整,不足0.5则为0
- */
- private BigDecimal roundHalfHour(double hours) {
- if (hours <= 0D) {
- return BigDecimal.ZERO.setScale(1, RoundingMode.HALF_UP);
- }
- double rounded = Math.floor(hours / 0.5D) * 0.5D;
- if (rounded < 0.5D) {
- rounded = 0D;
- }
- return BigDecimal.valueOf(rounded).setScale(1, RoundingMode.HALF_UP);
- }
- /**
- * 合计行:汇总全部加班工时
- */
- private Map<String, Object> calculateSummary(List<Map<String, Object>> dataList) {
- Map<String, Object> summary = new LinkedHashMap<String, Object>();
- summary.put("month", "合计"); // month: 合计行
- summary.put("personNumber", ""); // personNumber: 合计行留空
- summary.put("centerName", ""); // centerName: 合计行留空
- summary.put("deptName", ""); // deptName: 合计行留空
- summary.put("personName", ""); // personName: 合计行留空
- BigDecimal totalWeekday = BigDecimal.ZERO;
- BigDecimal totalWeekend = BigDecimal.ZERO;
- BigDecimal totalHours = BigDecimal.ZERO;
- for (Map<String, Object> row : dataList) {
- totalWeekday = totalWeekday.add((BigDecimal) row.get("weekdayOtHours"));
- totalWeekend = totalWeekend.add((BigDecimal) row.get("weekendOtHours"));
- totalHours = totalHours.add((BigDecimal) row.get("totalOtHours"));
- }
- summary.put("weekdayOtHours", totalWeekday); // weekdayOtHours: 平时加班合计
- summary.put("weekendOtHours", totalWeekend); // weekendOtHours: 周末加班合计
- summary.put("totalOtHours", totalHours); // totalOtHours: 加班合计
- return summary;
- }
- /** 按月加班工时(按 yyyy-MM 汇总) */
- private static class MonthOtHours {
- BigDecimal weekdayHours = BigDecimal.ZERO;
- BigDecimal weekendHours = BigDecimal.ZERO;
- }
- /**
- * 间接人员行数据,承载报表行中的工号/部门/姓名/中心。
- */
- private static class PersonRow {
- /**
- * 人员FID,作为 punchMap/offDutyMap 的外层key
- */
- String personId;
- /**
- * 工号 -> personNumber
- */
- String personNumber;
- /**
- * 姓名 -> personName
- */
- String personName;
- /**
- * 部门名称 -> deptName
- */
- String deptName;
- /**
- * 所属中心名称 -> centerName(列表列名沿用centerName)
- */
- String centerName;
- }
- /**
- * 排序:月份 -> 部门 -> 姓名
- */
- private static class RowComparator implements Comparator<Map<String, Object>> {
- public int compare(Map<String, Object> left, Map<String, Object> right) {
- String leftMonth = String.valueOf(left.get("month"));
- String rightMonth = String.valueOf(right.get("month"));
- int monthCompare = leftMonth.compareTo(rightMonth);
- if (monthCompare != 0) {
- return monthCompare;
- }
- String leftDept = String.valueOf(left.get("_sortDept"));
- String rightDept = String.valueOf(right.get("_sortDept"));
- int deptCompare = leftDept.compareTo(rightDept);
- if (deptCompare != 0) {
- return deptCompare;
- }
- String leftName = String.valueOf(left.get("_sortName"));
- String rightName = String.valueOf(right.get("_sortName"));
- return leftName.compareTo(rightName);
- }
- }
- }
|