Преглед изворни кода

考勤改造:
一.允许修改上班打卡时间,下班打卡时间,备注,考勤状态,排班类型
二.字段修改逻辑如下:
1.上班卡时间修改时间必须等于或者在配置的上班时间之前
2.下班卡时间修改时间必须等于或者在配置的下班卡时间之后
3.修改的上下班时间控制间隔低于24小时且上班卡要和配置的上班时间为同一日,下班卡根据配置是否跨日也有控制(非跨日为同一日,跨日为第二日)
4.考勤状态只能修改为正常
且修改考勤状态会同步修改上班/下班打卡状态为正常
如果迟到或者早退,迟到早退时长均清零,打卡时间也会修改为配置正常上下班的时间(如果早退下班卡打卡时间会修改为配置的下班卡时间,如果迟到上班卡会修改为配置的上班卡时间)
5.排班类型只能修改为配置中已生效的排班类型

xiexh пре 4 дана
родитељ
комит
1a37446167

+ 290 - 0
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/components/employee/domain/AttendanceDetail.java

@@ -653,4 +653,294 @@ public class AttendanceDetail extends AbstractDomain<String, AttendanceDetailPo>
 			LOGGER.error("打卡触发修正考勤状态失败", e);
 		}
 	}
+
+	/**
+	 * 改造后的修改考勤记录逻辑:
+	 *1.上班卡时间修改时间必须等于或者在配置的上班时间之前
+	 *2.下班卡时间修改时间必须等于或者在配置的下班卡时间之后
+	 *3.考勤状态只能修改为正常
+	    且修改考勤状态会同步修改上班/下班打卡状态为正常
+	    如果迟到或者早退,迟到早退时长均清零,打卡时间也会修改为配置正常上下班的时间(如果早退下班卡打卡时间会修改为配置的下班卡时间,如果迟到上班卡会修改为配置的上班卡时间)
+	 *4.排班类型只能修改为配置中已生效的排班类型
+	 * @param po 传入参数 记录id, 上班时间, 下班时间, 备注, 排班类型, 考勤状态
+	 * @return 填充后的考勤记录对象
+	 * @throws Exception 校验或业务异常
+	 */
+	public AttendanceDetailPo gdUpdateAttendce2(AttendanceDetailPo po) throws Exception {
+		// 1. 参数校验
+		if (po == null || BeanUtils.isEmpty(po.getId())) {
+			throw new Exception("ID不能为空");
+		}
+		String id = po.getId();
+
+		// 2. 查询考勤记录
+		AttendanceDetailPo records = attendanceDetailRepository.get(id);
+		if (BeanUtils.isEmpty(records)) {
+			throw new Exception("考勤记录不存在");
+		}
+
+		// 3. 处理排班类型修改逻辑
+		String buMen = records.getBuMen();
+		String peiZhiMingChen = records.getPaiBanMingChen();
+		String currentBanCiMing = records.getBanCiMing();
+		String newBanCiMing = po.getBanCiMing();
+
+		// 确定最终使用的排班类型
+		String finalBanCiMing = BeanUtils.isNotEmpty(newBanCiMing) ? newBanCiMing : currentBanCiMing;
+		boolean isBanCiMingModified = BeanUtils.isNotEmpty(newBanCiMing) && !newBanCiMing.equals(currentBanCiMing);
+
+		// 查询考勤配置 (基于最终的班次类型)
+		Map<String, String> configWhere = new HashMap<>();
+		configWhere.put("pai_ban_bu_men_", buMen);// 对应打卡记录的 bu_men_
+		configWhere.put("pei_zhi_ming_chen", peiZhiMingChen); //对应打卡记录的 pai_ban_ming_chen
+		configWhere.put("pai_ban_lei_xing_", finalBanCiMing); //对应打卡记录的 ban_ci_ming_
+		configWhere.put("sheng_xiao_biao_z", "Y");
+
+		List<Map<String, Object>> configList = updateDataTableDao.selectDataTable("t_gdpbpzb", configWhere, null);
+		if (BeanUtils.isEmpty(configList)) {
+			// 明确提示是传入的新排班类型无效,还是原记录配置失效
+			if (isBanCiMingModified) {
+				throw new Exception("排班类型[" + newBanCiMing + "]未找到生效的考勤配置,请检查是否已生效");
+			} else {
+				throw new Exception("未找到生效的考勤配置,请检查部门[" + buMen + "]、配置名称[" + peiZhiMingChen + "]、排班类型[" + finalBanCiMing + "]是否存在");
+			}
+		}
+		if (configList.size() > 1) {
+			throw new Exception("考勤配置存在多条记录,请联系管理员");
+		}
+		Map<String, Object> configMap = configList.get(0);
+		String shangBanShiJianPz = (String) configMap.get("shang_ban_shi_jia");
+		String xiaBanShiJianPz = (String) configMap.get("xia_ban_shi_jian_");
+		String shiFouKuaRiPz = (String) configMap.get("shi_fou_kua_ri_");
+
+		// 4. 解析日期
+		String riQi = records.getRiQi();
+		LocalDate recordDate;
+		try {
+			recordDate = LocalDate.parse(riQi, DATE_FORMATTER);
+		} catch (DateTimeParseException e) {
+			throw new Exception("考勤记录日期格式错误");
+		}
+
+		// 5. 获取用户传入的新值
+		String newDaKa1 = po.getDaKaShiJian1();
+		String newDaKa2 = po.getDaKaShiJian2();
+		String newBeiZhu = po.getBeiZhu();
+		String inputKaoQinZhuangTai = po.getKaoQinZhuangTa(); // 获取传入的考勤状态
+
+		// 标记字段修改状态
+		boolean isDaKa1Modified = false;
+		boolean isDaKa2Modified = false;
+		boolean isBeiZhuModified = false;
+
+		// 判断是否为外勤模式(即强制修改为“正常”状态)
+		boolean isWaiQinMode = false;
+		if (BeanUtils.isNotEmpty(inputKaoQinZhuangTai)) {
+			if ("正常".equals(inputKaoQinZhuangTai)) {
+				isWaiQinMode = true;
+			} else {
+				throw new Exception("考勤状态只允许修改为正常");
+			}
+		}
+
+		// 预计算配置的标准上下班时间,供后续逻辑复用
+		LocalDateTime configStartTime = LocalDateTime.parse(riQi + " " + shangBanShiJianPz + ":00", DATETIME_FORMATTER);
+		LocalDate configEndDate = "Y".equals(shiFouKuaRiPz) ? recordDate.plusDays(1) : recordDate;
+		LocalDateTime configEndTime = LocalDateTime.parse(configEndDate.format(DATE_FORMATTER) + " " + xiaBanShiJianPz + ":00", DATETIME_FORMATTER);
+
+		// 增加标记:记录原本的打卡时间是否合规(用于外勤模式下判断是否需要重置时间)
+		boolean isOriginalDaKa1Valid = false;
+		boolean isOriginalDaKa2Valid = false;
+
+		// 6. 处理上班打卡时间修改
+		if (BeanUtils.isNotEmpty(newDaKa1)) {
+			LocalDateTime newDaKa1Time;
+			try {
+				newDaKa1Time = LocalDateTime.parse(newDaKa1, DATETIME_FORMATTER);
+			} catch (DateTimeParseException e) {
+				throw new Exception("上班打卡时间格式错误");
+			}
+			// 【新增校验】上班卡日期必须与考勤记录日期(recordDate)为同一日
+			if (!newDaKa1Time.toLocalDate().isEqual(recordDate)) {
+				throw new Exception("上班打卡日期必须为考勤记录当天(" + riQi + ")");
+			}
+			// 校验:修改后的时间不能晚于配置上班时间
+			if (configStartTime.isBefore(newDaKa1Time)) {
+				throw new Exception("上班卡不允许修改比配置的上班时间{" + configStartTime.format(DATETIME_FORMATTER) + "}晚");
+			}
+			// 判断当前传入的上班卡是否合规(即没有迟到)
+			if (!newDaKa1Time.isAfter(configStartTime)) {
+				records.setZhuangTai1("正常");
+				records.setChiDaoShiChang(0L);
+				isOriginalDaKa1Valid = true; // 标记为合规
+			}
+			records.setDaKaShiJian1(newDaKa1);
+			isDaKa1Modified = true;
+		} else {// (上班迟到一分钟不算迟到,这种数据在外勤模式修改会被修改上班时间)
+			// 如果没有传入新时间,检查数据库原有时间是否合规
+			if (BeanUtils.isNotEmpty(records.getDaKaShiJian1())) {
+				LocalDateTime originalTime = LocalDateTime.parse(records.getDaKaShiJian1(), DATETIME_FORMATTER);
+				if (!originalTime.isAfter(configStartTime)) {
+					isOriginalDaKa1Valid = true;
+				}
+			}
+		}
+
+		// 7. 处理下班打卡时间修改
+		if (BeanUtils.isNotEmpty(newDaKa2)) {
+			LocalDateTime newDaKa2Time;
+			try {
+				newDaKa2Time = LocalDateTime.parse(newDaKa2, DATETIME_FORMATTER);
+			} catch (DateTimeParseException e) {
+				throw new Exception("下班打卡时间格式错误");
+			}
+			// 【新增校验】下班卡日期根据是否跨日进行控制
+			LocalDate expectedCheckOutDate = "Y".equals(shiFouKuaRiPz) ? recordDate.plusDays(1) : recordDate;
+			if (!newDaKa2Time.toLocalDate().isEqual(expectedCheckOutDate)) {
+				String expectedDateStr = expectedCheckOutDate.format(DATE_FORMATTER);
+				String crossDayDesc = "Y".equals(shiFouKuaRiPz) ? "(跨日班次应为次日" + expectedDateStr + ")" : "(非跨日班次应为当天" + expectedDateStr + ")";
+				throw new Exception("下班打卡日期不正确" + crossDayDesc);
+			}
+			// 校验:修改后的时间不能早于配置下班时间
+			if (configEndTime.isAfter(newDaKa2Time)) {
+				throw new Exception("下班卡{" + newDaKa2Time.format(DATETIME_FORMATTER) + "}不允许修改的比配置的下班时间{" + configEndTime.format(DATETIME_FORMATTER) + "}早");
+			}
+			// 判断当前传入的下班卡是否合规(即没有早退)
+			if (!newDaKa2Time.isBefore(configEndTime)) {
+				records.setZhuangTai2("正常");
+				long jiaBan = Duration.between(configEndTime, newDaKa2Time).toMinutes();
+				records.setJiaBanShiChang(jiaBan);
+				isOriginalDaKa2Valid = true; // 标记为合规
+			}
+			records.setDaKaShiJian2(newDaKa2);
+			isDaKa2Modified = true;
+		} else {
+			// 如果没有传入新时间,检查数据库原有时间是否合规
+			if (BeanUtils.isNotEmpty(records.getDaKaShiJian2())) {
+				LocalDateTime originalTime = LocalDateTime.parse(records.getDaKaShiJian2(), DATETIME_FORMATTER);
+				if (!originalTime.isBefore(configEndTime)) {
+					isOriginalDaKa2Valid = true;
+				}
+			}
+		}
+
+		// 8. 处理备注
+		if (BeanUtils.isNotEmpty(newBeiZhu)) {
+			records.setBeiZhu(newBeiZhu);
+			isBeiZhuModified = true;
+		}
+
+		// 9. 处理排班类型更新
+		if (isBanCiMingModified) {
+			records.setBanCiMing(newBanCiMing);
+		}
+
+		// 10. 处理考勤状态(核心修改逻辑:仅当时间不合规时才强制重置)
+		if (isWaiQinMode) {
+			// 强制将状态设为正常,时长清零
+			records.setZhuangTai1("正常");
+			records.setZhuangTai2("正常");
+			records.setKaoQinZhuangTa("正常");
+			records.setChiDaoShiChang(0L);
+			//records.setJiaBanShiChang(0L);
+
+			// 【核心优化】只有当上班卡不合规时,才重置为配置的上班时间
+			if (!isOriginalDaKa1Valid) {
+				records.setDaKaShiJian1(configStartTime.format(DATETIME_FORMATTER));
+				isDaKa1Modified = true;
+			}
+
+			// 【核心优化】只有当下班卡不合规时,才重置为配置的下班时间
+			if (!isOriginalDaKa2Valid) {
+				records.setDaKaShiJian2(configEndTime.format(DATETIME_FORMATTER));
+				isDaKa2Modified = true;
+			}
+
+		} else {
+			// 正常出勤模式:根据上下班状态自动汇总考勤总状态
+			String zt1 = records.getZhuangTai1();
+			String zt2 = records.getZhuangTai2();
+			if ("正常".equals(zt1) && "正常".equals(zt2)) {
+				records.setKaoQinZhuangTa("正常");
+			} else {
+				records.setKaoQinZhuangTa("异常");
+			}
+		}
+
+		// 11. 重新计算统计字段
+		// 11.1 重新计算班次时长
+		long newBanCiShiChang = Duration.between(configStartTime, configEndTime).toMinutes();
+		records.setBanCiShiChang(newBanCiShiChang);
+
+		// 11.2 计算工作时长 (基于打卡时间)
+		long gongZuoShiChang = 0;
+		LocalDateTime daKa1Time = null;
+		LocalDateTime daKa2Time = null;
+
+		if (isDaKa1Modified) {//有被修改取上送,没有被修改取数据库非空数据
+			daKa1Time = LocalDateTime.parse(records.getDaKaShiJian1(), DATETIME_FORMATTER);
+		} else if (BeanUtils.isNotEmpty(records.getDaKaShiJian1())) {
+			daKa1Time = LocalDateTime.parse(records.getDaKaShiJian1(), DATETIME_FORMATTER);
+		}
+
+		if (isDaKa2Modified) {
+			daKa2Time = LocalDateTime.parse(records.getDaKaShiJian2(), DATETIME_FORMATTER);
+		} else if (BeanUtils.isNotEmpty(records.getDaKaShiJian2())) {
+			daKa2Time = LocalDateTime.parse(records.getDaKaShiJian2(), DATETIME_FORMATTER);
+		}
+
+		if (daKa1Time != null && daKa2Time != null) {
+			// 校验下班不能早于上班
+			if (daKa2Time.isBefore(daKa1Time)) {
+				throw new Exception("下班打卡时间不能早于上班打卡时间");
+			}
+			// 【新增校验】上下班打卡时间间隔必须低于24小时
+			long hoursDiff = Duration.between(daKa1Time, daKa2Time).toHours();
+			if (hoursDiff >= 24) {
+				throw new Exception("上下班打卡时间间隔不能超过24小时,当前间隔为:" + hoursDiff + "小时");
+			}
+			gongZuoShiChang = Duration.between(daKa1Time, daKa2Time).toMinutes();
+		}
+		records.setGongZuoShiChan(gongZuoShiChang);//实际工作时长
+
+		// 12. 构建更新条件
+		Map<String, String> updateCond = new HashMap<>();
+
+		if (isDaKa1Modified) {
+			updateCond.put("da_ka_shi_jian_1_", records.getDaKaShiJian1());
+			// 状态和迟到时长总是更新(外勤模式下会被设为正常和0)
+			updateCond.put("zhuang_tai_1_", records.getZhuangTai1());
+			updateCond.put("chi_dao_shi_chang", String.valueOf(records.getChiDaoShiChang()));
+		}
+		if (isDaKa2Modified) {
+			updateCond.put("da_ka_shi_jian_2_", records.getDaKaShiJian2());
+			updateCond.put("zhuang_tai_2_", records.getZhuangTai2());
+			updateCond.put("jia_ban_shi_chang", String.valueOf(records.getJiaBanShiChang()));
+		}
+		if (isBeiZhuModified) {
+			updateCond.put("bei_zhu_", records.getBeiZhu());
+		}
+		if (isBanCiMingModified) {
+			updateCond.put("ban_ci_ming_", records.getBanCiMing());
+			updateCond.put("shi_fou_kua_ri_", shiFouKuaRiPz);
+		}
+
+		// 总是更新统计字段和考勤状态
+		updateCond.put("gong_zuo_shi_chan", String.valueOf(records.getGongZuoShiChan()));
+		// 客户现在允许修改排班配置,班次时长可能会变动
+		updateCond.put("ban_ci_shi_chang_", String.valueOf(records.getBanCiShiChang()));
+		updateCond.put("kao_qin_zhuang_ta", records.getKaoQinZhuangTa());
+		// 修改成功过的数据需要做一下标记
+		updateCond.put("tenant_id_", "999999-"+recordDate.format(DATE_FORMATTER));
+		updateCond.put("update_time_", LocalDateTime.now().format(DATETIME_FORMATTER));
+
+		Map<String, String> whereCond = Collections.singletonMap("id_", id);
+		int updated = updateDataTableDao.updateDataTable("t_attendance_detail", updateCond, whereCond);
+
+		if (updated != 1) {
+			throw new Exception("更新考勤记录失败");
+		}
+
+		return records;
+	}
 }

+ 1 - 1
ibps-provider-root/modules/provider-business/src/main/java/com/lc/ibps/components/employee/provider/AttendanceDetailProvider.java

@@ -173,7 +173,7 @@ public class AttendanceDetailProvider extends GenericProvider implements IAttend
 		try {
 			logger.info(" com.lc.ibps.components.provider.AttendanceDetailProvider.gdUpdateAttendce()--->attendanceDetailPo: {}", po.toString());
 			//修改
-			AttendanceDetailPo data = attendanceDetail.gdUpdateAttendce(po);
+			AttendanceDetailPo data = attendanceDetail.gdUpdateAttendce2(po);
 			result.setMessage("更新考勤打卡时间成功");
 		} catch (Exception e) {
 			result.setMessage("更新考勤打卡时间失败");