|
|
@@ -0,0 +1,645 @@
|
|
|
+package com.kingdee.eas.custom.ats.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.bos.metadata.entity.FilterInfo;
|
|
|
+import com.kingdee.eas.util.app.DbUtil;
|
|
|
+import com.kingdee.jdbc.rowset.IRowSet;
|
|
|
+import com.kingdee.shr.base.syssetting.context.SHRContext;
|
|
|
+import com.kingdee.shr.base.syssetting.exception.SHRWebException;
|
|
|
+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 com.kingdee.util.DateTimeUtils;
|
|
|
+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.*;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 类名称: AttendancePunchStatListHandler
|
|
|
+ * 功能描述: 考勤打卡统计列表处理器(结合排班:有排班未打卡显示未打卡,未排班显示空)
|
|
|
+ * 创建日期: 2026-05-29
|
|
|
+ * 作 者: 青梧
|
|
|
+ * 版 本: 1.0
|
|
|
+ */
|
|
|
+public class AttendancePunchStatListHandler extends ListHandler {
|
|
|
+ private static Logger logger = Logger.getLogger(AttendancePunchStatListHandler.class);
|
|
|
+ /** 有排班但未打卡时的展示文案 */
|
|
|
+ private static final String NOT_PUNCHED_LABEL = "未打卡";
|
|
|
+ /**
|
|
|
+ * 排班日类型:工作日(需打卡)。若与现场枚举不一致可调整此值。
|
|
|
+ */
|
|
|
+ private static final int SCHEDULE_WORK_DAY_TYPE = 0;
|
|
|
+
|
|
|
+ Context ctx = SHRContext.getInstance().getContext();
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected GridDataEntity getGridRequestData(HttpServletRequest request, HttpServletResponse response,
|
|
|
+ ModelMap modelMap) throws SHRWebException {
|
|
|
+ logger.info("getGridRequestData------------");
|
|
|
+
|
|
|
+ // 1. 获取快速过滤条件(直接从request参数获取框架生成的SQL字符串)
|
|
|
+ String filterItemsStr = request.getParameter("filterItems");
|
|
|
+ logger.info("filterItems from parameter: " + filterItemsStr);
|
|
|
+
|
|
|
+ // 2. 获取年月信息(从fastFilterItems JSON中解析)
|
|
|
+ String fastFilterItems = request.getParameter("fastFilterItems");
|
|
|
+ String yearMonth = "";
|
|
|
+
|
|
|
+ if (!StringUtils.isEmpty(fastFilterItems)) {
|
|
|
+ try {
|
|
|
+ JSONObject filterJson = JSONObject.parseObject(fastFilterItems);
|
|
|
+ if (filterJson.containsKey("yearMonth")) {
|
|
|
+ Object yearMonthObj = filterJson.get("yearMonth");
|
|
|
+ if (yearMonthObj instanceof JSONObject) {
|
|
|
+ JSONObject yearMonthJson = (JSONObject) yearMonthObj;
|
|
|
+ // 尝试获取 values 字段
|
|
|
+ Object valuesObj = yearMonthJson.get("values");
|
|
|
+ if (valuesObj instanceof JSONObject) {
|
|
|
+ // 还有一层嵌套,继续解析
|
|
|
+ JSONObject valuesJson = (JSONObject) valuesObj;
|
|
|
+ if (valuesJson.containsKey("date")) {
|
|
|
+ yearMonth = valuesJson.getString("date");
|
|
|
+ }
|
|
|
+ } else if (valuesObj instanceof String) {
|
|
|
+ yearMonth = (String) valuesObj;
|
|
|
+ }
|
|
|
+ } else if (yearMonthObj instanceof String) {
|
|
|
+ yearMonth = (String) yearMonthObj;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("解析fastFilterItems失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 如果年月为空,使用当前年月
|
|
|
+ if (StringUtils.isEmpty(yearMonth)) {
|
|
|
+ yearMonth = DateTimeUtils.format(new Date(), "yyyy-MM");
|
|
|
+ }
|
|
|
+ logger.info("最终yearMonth: " + yearMonth);
|
|
|
+
|
|
|
+ try {
|
|
|
+ GridDataEntity gridDataEntity = new GridDataEntity();
|
|
|
+ int rows = Integer.parseInt(request.getParameter("rows"));// 行数
|
|
|
+ int page = Integer.parseInt(request.getParameter("page"));// 页数
|
|
|
+ int rowsNum = rows;
|
|
|
+
|
|
|
+ // 解析年月,支持 "yyyy-MM" 或 "yyyy-MM-dd" 格式
|
|
|
+ String[] parts = yearMonth.split("-");
|
|
|
+ int year = Integer.parseInt(parts[0]);
|
|
|
+ int month = Integer.parseInt(parts[1]);
|
|
|
+
|
|
|
+ int daysInMonth = getDaysInMonth(year, month);
|
|
|
+
|
|
|
+ // 查询打卡记录(使用filterItems进行过滤)
|
|
|
+ String sql = getSql(year, month, filterItemsStr);
|
|
|
+ logger.info("考勤打卡统计SQL: " + sql);
|
|
|
+ IRowSet rs = DbUtil.executeQuery(ctx, sql);
|
|
|
+
|
|
|
+ // 封装打卡数据
|
|
|
+ Map<String, Object> packagedData = packagingPunchData(rs);
|
|
|
+ Map<String, Map<Integer, String>> personPunchDataMap = (Map<String, Map<Integer, String>>) packagedData
|
|
|
+ .get("punchData");
|
|
|
+ Map<String, String> personCodeMap = (Map<String, String>) packagedData.get("personCodeMap");
|
|
|
+ Map<String, String> personNameMap = (Map<String, String>) packagedData.get("personNameMap");
|
|
|
+
|
|
|
+ // 查询当月排班(工作日),与打卡数据合并
|
|
|
+ String scheduleSql = getScheduleSql(year, month, filterItemsStr);
|
|
|
+ logger.info("排班查询SQL: " + scheduleSql);
|
|
|
+ IRowSet scheduleRs = DbUtil.executeQuery(ctx, scheduleSql);
|
|
|
+ Map<String, Set<Integer>> personScheduleDays = packagingScheduleData(scheduleRs, personCodeMap,
|
|
|
+ personNameMap);
|
|
|
+
|
|
|
+ // 处理数据:有排班无打卡显示「未打卡」,未排班显示空
|
|
|
+ List<Map<String, Object>> list = disposeData(personPunchDataMap, personScheduleDays, personCodeMap,
|
|
|
+ personNameMap, daysInMonth);
|
|
|
+
|
|
|
+ int dataCount = list.size();
|
|
|
+ int total = dataCount % rowsNum == 0 ? dataCount / rowsNum : dataCount / rowsNum + 1;
|
|
|
+
|
|
|
+ if (list != null && list.size() > 0) {
|
|
|
+ gridDataEntity.setTotal(total);// 总页数
|
|
|
+ gridDataEntity.setPage(page);// 当前页数
|
|
|
+ gridDataEntity.setRecords(dataCount);// 总记录数
|
|
|
+ gridDataEntity.setRows(list);
|
|
|
+ gridDataEntity.setUserdata(new HashMap<String, Object>());
|
|
|
+ }
|
|
|
+
|
|
|
+ return gridDataEntity;
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ throw new ShrWebBizException(e.getMessage());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建SQL查询语句
|
|
|
+ *
|
|
|
+ * @param year 年
|
|
|
+ * @param month 月
|
|
|
+ * @param filterItemsStr 框架生成的SQL过滤条件字符串,如: "( personCode like '%阿萨德%' )"
|
|
|
+ */
|
|
|
+ private String getSql(int year, int month, String filterItemsStr) {
|
|
|
+ // 计算当月的起始和结束日期
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.set(year, month - 1, 1, 0, 0, 0);
|
|
|
+ cal.set(Calendar.MILLISECOND, 0);
|
|
|
+ String startDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
+ cal.set(year, month, 1, 0, 0, 0); // 直接设置下个月第一天
|
|
|
+ cal.set(Calendar.MILLISECOND, 0);
|
|
|
+ String endDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
+ StringBuilder sql = new StringBuilder();
|
|
|
+ sql.append("SELECT ");
|
|
|
+ sql.append(" p.FNUMBER AS personCode, ");
|
|
|
+ sql.append(" p.FNAME_L2 AS personName, ");
|
|
|
+ sql.append(" p.FID AS personId, ");
|
|
|
+ sql.append(" pcr.FPUNCHCARDDATE AS fpunchcarddate, ");
|
|
|
+ sql.append(" pcr.FPUNCHCARDTIME AS punchTime ");
|
|
|
+ sql.append("FROM T_HR_ATS_PunchCardRecord pcr ");
|
|
|
+ sql.append("LEFT JOIN T_BD_PERSON p ON p.FID = pcr.FPROPOSERID ");
|
|
|
+ sql.append("WHERE pcr.FPUNCHCARDDATE >= {ts '").append(startDate).append("'} ");
|
|
|
+ sql.append("AND pcr.FPUNCHCARDDATE < {ts '").append(endDate).append("'} ");
|
|
|
+
|
|
|
+ // 添加框架生成的过滤条件(员工编码、姓名等)
|
|
|
+ // 注意:需要将前端字段名映射为数据库字段名
|
|
|
+ if (!StringUtils.isEmpty(filterItemsStr)) {
|
|
|
+ // 替换字段名:personCode -> p.FNUMBER, personName -> p.FNAME_L2
|
|
|
+ String mappedFilterItems = filterItemsStr
|
|
|
+ .replace("personCode", "p.FNUMBER")
|
|
|
+ .replace("personName", "p.FNAME_L2");
|
|
|
+ sql.append(" AND ").append(mappedFilterItems);
|
|
|
+ }
|
|
|
+
|
|
|
+ sql.append(" ORDER BY p.FNUMBER, pcr.FPUNCHCARDDATE, pcr.FPUNCHCARDTIME");
|
|
|
+
|
|
|
+ return sql.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建排班查询SQL(查询时间范围内人员的工作日排班)
|
|
|
+ */
|
|
|
+ private String getScheduleSql(int year, int month, String filterItemsStr) {
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.set(year, month - 1, 1, 0, 0, 0);
|
|
|
+ cal.set(Calendar.MILLISECOND, 0);
|
|
|
+ String startDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
+ cal.set(year, month, 1, 0, 0, 0);
|
|
|
+ cal.set(Calendar.MILLISECOND, 0);
|
|
|
+ String endDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
+ StringBuilder sql = new StringBuilder();
|
|
|
+ sql.append("SELECT ");
|
|
|
+ sql.append(" p.FID AS personId, ");
|
|
|
+ sql.append(" p.FNUMBER AS personCode, ");
|
|
|
+ sql.append(" p.FNAME_L2 AS personName, ");
|
|
|
+ sql.append(" ss.FATTENDDATE AS attendDate ");
|
|
|
+ sql.append("FROM T_HR_ATS_SCHEDULESHIFT ss ");
|
|
|
+ sql.append("INNER JOIN T_BD_PERSON p ON p.FID = ss.FPROPOSERID ");
|
|
|
+ sql.append("WHERE ss.FATTENDDATE >= {ts '").append(startDate).append("'} ");
|
|
|
+ sql.append("AND ss.FATTENDDATE < {ts '").append(endDate).append("'} ");
|
|
|
+ sql.append("AND ss.FDAYTYPE = ").append(SCHEDULE_WORK_DAY_TYPE).append(" ");
|
|
|
+
|
|
|
+ if (!StringUtils.isEmpty(filterItemsStr)) {
|
|
|
+ String mappedFilterItems = filterItemsStr
|
|
|
+ .replace("personCode", "p.FNUMBER")
|
|
|
+ .replace("personName", "p.FNAME_L2");
|
|
|
+ sql.append(" AND ").append(mappedFilterItems);
|
|
|
+ }
|
|
|
+
|
|
|
+ sql.append(" ORDER BY p.FNUMBER, ss.FATTENDDATE");
|
|
|
+ return sql.toString();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 封装排班数据:personId -> 当月有工作排班的日期集合(1-31)
|
|
|
+ */
|
|
|
+ private Map<String, Set<Integer>> packagingScheduleData(IRowSet rs, Map<String, String> personCodeMap,
|
|
|
+ Map<String, String> personNameMap) throws SQLException {
|
|
|
+ Map<String, Set<Integer>> scheduleMap = new HashMap<>();
|
|
|
+
|
|
|
+ while (rs.next()) {
|
|
|
+ String personId = rs.getString("personId");
|
|
|
+ if (StringUtils.isEmpty(personId)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ String personCode = rs.getString("personCode");
|
|
|
+ String personName = rs.getString("personName");
|
|
|
+ if (!personCodeMap.containsKey(personId)) {
|
|
|
+ personCodeMap.put(personId, personCode);
|
|
|
+ }
|
|
|
+ if (!personNameMap.containsKey(personId)) {
|
|
|
+ personNameMap.put(personId, personName);
|
|
|
+ }
|
|
|
+
|
|
|
+ java.sql.Date attendDate = rs.getDate("attendDate");
|
|
|
+ if (attendDate == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Calendar dateCal = Calendar.getInstance();
|
|
|
+ dateCal.setTime(attendDate);
|
|
|
+ int day = dateCal.get(Calendar.DAY_OF_MONTH);
|
|
|
+ if (day < 1 || day > 31) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!scheduleMap.containsKey(personId)) {
|
|
|
+ scheduleMap.put(personId, new HashSet<Integer>());
|
|
|
+ }
|
|
|
+ scheduleMap.get(personId).add(day);
|
|
|
+ }
|
|
|
+
|
|
|
+ return scheduleMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ private int getDaysInMonth(int year, int month) {
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.set(year, month - 1, 1);
|
|
|
+ return cal.getActualMaximum(Calendar.DAY_OF_MONTH);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 封装打卡数据(返回打卡数据和人员信息)
|
|
|
+ *
|
|
|
+ * @return Map<"punchData"=打卡数据Map, "personCodeMap"=人员编码Map, "personNameMap"=
|
|
|
+ * 人员姓名Map>
|
|
|
+ */
|
|
|
+ private Map<String, Object> packagingPunchData(IRowSet rs) throws SQLException {
|
|
|
+ // 临时存储每个人的原始打卡记录
|
|
|
+ Map<String, Map<Integer, List<String>>> tempDataMap = new HashMap<>();
|
|
|
+ // 存储人员编码
|
|
|
+ Map<String, String> personCodeMap = new HashMap<>();
|
|
|
+ // 存储人员姓名
|
|
|
+ Map<String, String> personNameMap = new HashMap<>();
|
|
|
+
|
|
|
+ while (rs.next()) {
|
|
|
+ // 人员ID
|
|
|
+ String personId = rs.getString("personId");
|
|
|
+ if (StringUtils.isEmpty(personId)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 人员编码
|
|
|
+ String personCode = rs.getString("personCode");
|
|
|
+
|
|
|
+ // 人员姓名
|
|
|
+ String personName = rs.getString("personName");
|
|
|
+
|
|
|
+ // 存储人员编码
|
|
|
+ if (!personCodeMap.containsKey(personId)) {
|
|
|
+ personCodeMap.put(personId, personCode);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 存储人员姓名
|
|
|
+ if (!personNameMap.containsKey(personId)) {
|
|
|
+ personNameMap.put(personId, personName);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从完整日期中提取日期(当月第几天,1-31)
|
|
|
+ java.sql.Date punchCardDate = rs.getDate("fpunchcarddate");
|
|
|
+ if (punchCardDate == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ java.util.Calendar dateCal = java.util.Calendar.getInstance();
|
|
|
+ dateCal.setTime(punchCardDate);
|
|
|
+ int punchDay = dateCal.get(java.util.Calendar.DAY_OF_MONTH);
|
|
|
+ if (punchDay < 1 || punchDay > 31) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 打卡时间
|
|
|
+ if (rs.getDate("punchTime") == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 格式化为 HH:mm:ss
|
|
|
+ String punchTime = DateTimeUtils.format(rs.getDate("punchTime"), "HH:mm:ss");
|
|
|
+
|
|
|
+ if (!tempDataMap.containsKey(personId)) {
|
|
|
+ tempDataMap.put(personId, new HashMap<>());
|
|
|
+ }
|
|
|
+ if (!tempDataMap.get(personId).containsKey(punchDay)) {
|
|
|
+ tempDataMap.get(personId).put(punchDay, new ArrayList<>());
|
|
|
+ }
|
|
|
+ tempDataMap.get(personId).get(punchDay).add(punchTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理每个人的打卡记录,过滤一分钟内的重复打卡
|
|
|
+ Map<String, Map<Integer, String>> resultMap = new HashMap<>();
|
|
|
+ for (Map.Entry<String, Map<Integer, List<String>>> personEntry : tempDataMap.entrySet()) {
|
|
|
+ String personId = personEntry.getKey();
|
|
|
+ Map<Integer, List<String>> dayDataMap = personEntry.getValue();
|
|
|
+
|
|
|
+ if (!resultMap.containsKey(personId)) {
|
|
|
+ resultMap.put(personId, new HashMap<>());
|
|
|
+ }
|
|
|
+
|
|
|
+ for (Map.Entry<Integer, List<String>> dayEntry : dayDataMap.entrySet()) {
|
|
|
+ int day = dayEntry.getKey();
|
|
|
+ List<String> punchTimes = dayEntry.getValue();
|
|
|
+
|
|
|
+ // 对打卡时间排序
|
|
|
+ Collections.sort(punchTimes);
|
|
|
+
|
|
|
+ // 过滤一分钟内的重复打卡,保留最早的
|
|
|
+ List<String> filteredTimes = filterDuplicatePunches(punchTimes);
|
|
|
+
|
|
|
+ // 将过滤后的打卡时间用换行符分隔
|
|
|
+ String punchTimeStr = String.join("\n", filteredTimes);
|
|
|
+ resultMap.get(personId).put(day, punchTimeStr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 返回打卡数据和人员信息
|
|
|
+ Map<String, Object> result = new HashMap<>();
|
|
|
+ result.put("punchData", resultMap);
|
|
|
+ result.put("personCodeMap", personCodeMap);
|
|
|
+ result.put("personNameMap", personNameMap);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理数据,转换为列表格式。
|
|
|
+ * 规则:有打卡显示打卡时间;有排班无打卡显示「未打卡」;未排班显示空。
|
|
|
+ */
|
|
|
+ private List<Map<String, Object>> disposeData(Map<String, Map<Integer, String>> personPunchDataMap,
|
|
|
+ Map<String, Set<Integer>> personScheduleDays, Map<String, String> personCodeMap,
|
|
|
+ Map<String, String> personNameMap, int daysInMonth) {
|
|
|
+ List<Map<String, Object>> list = new ArrayList<>();
|
|
|
+
|
|
|
+ Set<String> allPersonIds = new HashSet<String>();
|
|
|
+ if (personPunchDataMap != null) {
|
|
|
+ allPersonIds.addAll(personPunchDataMap.keySet());
|
|
|
+ }
|
|
|
+ if (personScheduleDays != null) {
|
|
|
+ allPersonIds.addAll(personScheduleDays.keySet());
|
|
|
+ }
|
|
|
+
|
|
|
+ for (String personId : allPersonIds) {
|
|
|
+ Map<Integer, String> punchData = personPunchDataMap != null && personPunchDataMap.containsKey(personId)
|
|
|
+ ? personPunchDataMap.get(personId) : null;
|
|
|
+ Set<Integer> scheduleDays = personScheduleDays != null && personScheduleDays.containsKey(personId)
|
|
|
+ ? personScheduleDays.get(personId) : null;
|
|
|
+
|
|
|
+ Map<String, Object> rowData = new HashMap<>();
|
|
|
+ rowData.put("id", personId);
|
|
|
+ rowData.put("personId", personId);
|
|
|
+
|
|
|
+ if (personCodeMap.containsKey(personId)) {
|
|
|
+ rowData.put("personCode", personCodeMap.get(personId));
|
|
|
+ }
|
|
|
+ if (personNameMap.containsKey(personId)) {
|
|
|
+ rowData.put("personName", personNameMap.get(personId));
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int day = 1; day <= daysInMonth; day++) {
|
|
|
+ String punchTime = (punchData != null && punchData.containsKey(day)) ? punchData.get(day) : "";
|
|
|
+ boolean hasSchedule = scheduleDays != null && scheduleDays.contains(day);
|
|
|
+
|
|
|
+ if (!StringUtils.isEmpty(punchTime)) {
|
|
|
+ rowData.put("day" + day, punchTime);
|
|
|
+ } else if (hasSchedule) {
|
|
|
+ rowData.put("day" + day, NOT_PUNCHED_LABEL);
|
|
|
+ } else {
|
|
|
+ rowData.put("day" + day, "");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 超出当月天数的列保持为空(如2月 day29-31)
|
|
|
+ for (int day = daysInMonth + 1; day <= 31; day++) {
|
|
|
+ rowData.put("day" + day, "");
|
|
|
+ }
|
|
|
+
|
|
|
+ list.add(rowData);
|
|
|
+ }
|
|
|
+
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重写快速过滤方法,正确处理所有快速过滤条件
|
|
|
+ * 注意:
|
|
|
+ * 1. filterItems 参数包含框架生成的 SQL 过滤条件字符串,如: "( personCode like '%阿萨德%' )"
|
|
|
+ * 2. fastFilterItems 参数包含原始 JSON 数据,用于获取 yearMonth 等业务参数
|
|
|
+ * 3. 不能返回空的FilterInfo,否则会丢失所有过滤条件
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ protected FilterInfo getFastFilter(HttpServletRequest request) throws SHRWebException {
|
|
|
+ try {
|
|
|
+ // 1. 从请求参数中获取filterItems(框架已生成的SQL过滤条件)
|
|
|
+ String filterItemsStr = request.getParameter("filterItems");
|
|
|
+ logger.info("filterItems from request: " + filterItemsStr);
|
|
|
+
|
|
|
+ // 2. 从fastFilterItems中获取yearMonth等业务参数(用于日期查询)
|
|
|
+ String fastFilterItems = request.getParameter("fastFilterItems");
|
|
|
+ if (!StringUtils.isEmpty(fastFilterItems)) {
|
|
|
+ try {
|
|
|
+ JSONObject filterJson = JSONObject.parseObject(fastFilterItems);
|
|
|
+ // 将年月信息设置到request属性中(供getGridRequestData使用)
|
|
|
+ if (filterJson.containsKey("yearMonth")) {
|
|
|
+ Object yearMonthObj = filterJson.get("yearMonth");
|
|
|
+ if (yearMonthObj instanceof JSONObject) {
|
|
|
+ JSONObject yearMonthJson = (JSONObject) yearMonthObj;
|
|
|
+ Object valuesObj = yearMonthJson.get("values");
|
|
|
+ if (valuesObj != null) {
|
|
|
+ request.setAttribute("yearMonth", valuesObj.toString());
|
|
|
+ }
|
|
|
+ } else if (yearMonthObj instanceof String) {
|
|
|
+ request.setAttribute("yearMonth", yearMonthObj);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("解析fastFilterItems失败", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 将filterItems设置到request属性中(供getGridRequestData使用)
|
|
|
+ if (!StringUtils.isEmpty(filterItemsStr)) {
|
|
|
+ request.setAttribute("filterItems", filterItemsStr);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 返回空的FilterInfo,因为我们使用自定义SQL,直接拼接filterItems字符串
|
|
|
+ return new FilterInfo();
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("处理快速过滤条件失败", e);
|
|
|
+ return new FilterInfo();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 查询考勤打卡数据
|
|
|
+ *
|
|
|
+ * @param yearMonth 年月 yyyy-MM
|
|
|
+ * @return Map<人员ID, Map<日期, 打卡时间列表>>
|
|
|
+ */
|
|
|
+ public Map<String, Map<Integer, String>> getAttendancePunchData(String yearMonth)
|
|
|
+ throws SQLException, BOSException {
|
|
|
+ Map<String, Map<Integer, String>> resultMap = new HashMap<>();
|
|
|
+
|
|
|
+ // 解析年月
|
|
|
+ String[] parts = yearMonth.split("-");
|
|
|
+ int year = Integer.parseInt(parts[0]);
|
|
|
+ int month = Integer.parseInt(parts[1]);
|
|
|
+
|
|
|
+ // 构建SQL查询语句 - 查询所有打卡记录
|
|
|
+ // 计算当月的起始和结束日期
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.set(year, month - 1, 1, 0, 0, 0);
|
|
|
+ cal.set(Calendar.MILLISECOND, 0);
|
|
|
+ String startDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
+ cal.set(year, month, 1, 0, 0, 0); // 直接设置下个月第一天
|
|
|
+ cal.set(Calendar.MILLISECOND, 0);
|
|
|
+ String endDate = DateTimeUtils.format(cal.getTime(), "yyyy-MM-dd HH:mm:ss");
|
|
|
+
|
|
|
+ StringBuilder sql = new StringBuilder();
|
|
|
+ sql.append("SELECT ");
|
|
|
+ sql.append(" p.FNUMBER AS personCode, ");
|
|
|
+ sql.append(" p.FNAME_L2 AS personName, ");
|
|
|
+ sql.append(" p.FID AS personId, ");
|
|
|
+ sql.append(" pcr.FPUNCHCARDDATE AS fpunchcarddate, ");
|
|
|
+ sql.append(" pcr.FPUNCHCARDTIME AS punchTime ");
|
|
|
+ sql.append("FROM T_HR_ATS_PunchCardRecord pcr ");
|
|
|
+ sql.append("LEFT JOIN T_BD_PERSON p ON p.FID = pcr.FPROPOSERID ");
|
|
|
+ sql.append("WHERE pcr.FPUNCHCARDDATE >= {ts '").append(startDate).append("'} ");
|
|
|
+ sql.append("AND pcr.FPUNCHCARDDATE < {ts '").append(endDate).append("'} ");
|
|
|
+ sql.append("ORDER BY p.FNUMBER, pcr.FPUNCHCARDDATE, pcr.FPUNCHCARDTIME");
|
|
|
+
|
|
|
+ logger.info("考勤打卡统计SQL: " + sql.toString());
|
|
|
+ logger.info("参数: year=" + year + ", month=" + month);
|
|
|
+
|
|
|
+ // 执行查询(不需要参数)
|
|
|
+ IRowSet rs = com.kingdee.eas.util.app.DbUtil.executeQuery(
|
|
|
+ SHRContext.getInstance().getContext(),
|
|
|
+ sql.toString());
|
|
|
+
|
|
|
+ // 临时存储每个人的原始打卡记录
|
|
|
+ Map<String, Map<Integer, List<String>>> tempDataMap = new HashMap<>();
|
|
|
+
|
|
|
+ while (rs.next()) {
|
|
|
+ String personId = rs.getString("personId");
|
|
|
+
|
|
|
+ // 从完整日期中提取日期(当月第几天,1-31)
|
|
|
+ java.sql.Date punchCardDate = rs.getDate("fpunchcarddate");
|
|
|
+ if (punchCardDate == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ java.util.Calendar dateCal = java.util.Calendar.getInstance();
|
|
|
+ dateCal.setTime(punchCardDate);
|
|
|
+ int punchDay = dateCal.get(java.util.Calendar.DAY_OF_MONTH);
|
|
|
+
|
|
|
+ String punchTime = rs.getDate("punchTime") != null
|
|
|
+ ? DateTimeUtils.format(rs.getDate("punchTime"), "HH:mm:ss")
|
|
|
+ : "";
|
|
|
+
|
|
|
+ if (!tempDataMap.containsKey(personId)) {
|
|
|
+ tempDataMap.put(personId, new HashMap<>());
|
|
|
+ }
|
|
|
+ if (!tempDataMap.get(personId).containsKey(punchDay)) {
|
|
|
+ tempDataMap.get(personId).put(punchDay, new ArrayList<>());
|
|
|
+ }
|
|
|
+ tempDataMap.get(personId).get(punchDay).add(punchTime);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 处理每个人的打卡记录,过滤一分钟内的重复打卡
|
|
|
+ for (Map.Entry<String, Map<Integer, List<String>>> personEntry : tempDataMap.entrySet()) {
|
|
|
+ String personId = personEntry.getKey();
|
|
|
+ Map<Integer, List<String>> dayDataMap = personEntry.getValue();
|
|
|
+
|
|
|
+ if (!resultMap.containsKey(personId)) {
|
|
|
+ resultMap.put(personId, new HashMap<>());
|
|
|
+ }
|
|
|
+
|
|
|
+ for (Map.Entry<Integer, List<String>> dayEntry : dayDataMap.entrySet()) {
|
|
|
+ int day = dayEntry.getKey();
|
|
|
+ List<String> punchTimes = dayEntry.getValue();
|
|
|
+
|
|
|
+ // 对打卡时间排序
|
|
|
+ Collections.sort(punchTimes);
|
|
|
+
|
|
|
+ // 过滤一分钟内的重复打卡,保留最早的
|
|
|
+ List<String> filteredTimes = filterDuplicatePunches(punchTimes);
|
|
|
+
|
|
|
+ // 将过滤后的打卡时间用换行符分隔
|
|
|
+ String punchTimeStr = String.join("\n", filteredTimes);
|
|
|
+ resultMap.get(personId).put(day, punchTimeStr);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return resultMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 过滤一分钟内的重复打卡,保留最早的一条
|
|
|
+ *
|
|
|
+ * @param punchTimes 已排序的打卡时间列表
|
|
|
+ * @return 过滤后的打卡时间列表
|
|
|
+ */
|
|
|
+ private List<String> filterDuplicatePunches(List<String> punchTimes) {
|
|
|
+ if (punchTimes == null || punchTimes.isEmpty()) {
|
|
|
+ return punchTimes;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> result = new ArrayList<>();
|
|
|
+
|
|
|
+ for (int i = 0; i < punchTimes.size(); i++) {
|
|
|
+ String currentTime = punchTimes.get(i);
|
|
|
+
|
|
|
+ // 如果是第一个元素,直接添加(最早的)
|
|
|
+ if (i == 0) {
|
|
|
+ result.add(currentTime);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ String previousTime = result.get(result.size() - 1);
|
|
|
+
|
|
|
+ // 计算两个时间之间的秒数差
|
|
|
+ long secondsDiff = calculateTimeDifference(previousTime, currentTime);
|
|
|
+
|
|
|
+ // 如果时间差大于60秒,添加当前记录
|
|
|
+ if (secondsDiff > 60) {
|
|
|
+ result.add(currentTime);
|
|
|
+ }
|
|
|
+ // 如果在同一分钟内,跳过当前记录(保留最早的一条)
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算两个时间字符串之间的秒数差
|
|
|
+ *
|
|
|
+ * @param time1 时间1 (HH:mm:ss)
|
|
|
+ * @param time2 时间2 (HH:mm:ss)
|
|
|
+ * @return 秒数差
|
|
|
+ */
|
|
|
+ private long calculateTimeDifference(String time1, String time2) {
|
|
|
+ try {
|
|
|
+ String[] parts1 = time1.split(":");
|
|
|
+ String[] parts2 = time2.split(":");
|
|
|
+
|
|
|
+ int seconds1 = Integer.parseInt(parts1[0]) * 3600 +
|
|
|
+ Integer.parseInt(parts1[1]) * 60 +
|
|
|
+ Integer.parseInt(parts1[2]);
|
|
|
+ int seconds2 = Integer.parseInt(parts2[0]) * 3600 +
|
|
|
+ Integer.parseInt(parts2[1]) * 60 +
|
|
|
+ Integer.parseInt(parts2[2]);
|
|
|
+
|
|
|
+ return Math.abs(seconds2 - seconds1);
|
|
|
+ } catch (Exception e) {
|
|
|
+ logger.error("计算时间差失败: " + time1 + ", " + time2, e);
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|