|
|
@@ -185,15 +185,21 @@ public class AttendanceDetail extends AbstractDomain<String, AttendanceDetailPo>
|
|
|
throw new Exception("考勤类型不能为空");
|
|
|
}
|
|
|
if (BeanUtils.isEmpty(clockType)) {
|
|
|
- throw new Exception("打卡类型不能为空,请传入 clockType=1(上班) 或 2(下班)");
|
|
|
+ throw new Exception("打卡类型不能为空,请传入 clockType=1(上班) 或 2(下班)或 3(补卡)");
|
|
|
}
|
|
|
- boolean isMorningClock;
|
|
|
+
|
|
|
+ boolean isMorningClock = false; //上班卡
|
|
|
+ boolean isClockOut = false; //下班卡
|
|
|
+ boolean isMendCard = false; //补卡
|
|
|
if ("1".equals(clockType)) {
|
|
|
isMorningClock = true;
|
|
|
} else if ("2".equals(clockType)) {
|
|
|
- isMorningClock = false;
|
|
|
- } else {
|
|
|
- throw new Exception("无效的打卡类型,应为 1(上班) 或 2(下班)");
|
|
|
+ isClockOut = true;
|
|
|
+ } else if("3".equals(clockType)){
|
|
|
+ isMendCard = true;
|
|
|
+ validateInfo(po);
|
|
|
+ }else {
|
|
|
+ throw new Exception("无效的打卡类型,应为 1(上班) 或 2(下班)或 3(补卡)");
|
|
|
}
|
|
|
// 2. 查询用户信息(检查用户是否存在,并获取工号)
|
|
|
Map<String, String> userParam = new HashMap<>();
|
|
|
@@ -213,17 +219,15 @@ public class AttendanceDetail extends AbstractDomain<String, AttendanceDetailPo>
|
|
|
configParam.put("pai_ban_bu_men_", buMen);
|
|
|
configParam.put("pei_zhi_ming_chen", paiBanMingChen);
|
|
|
configParam.put("pai_ban_lei_xing_", banCiMing);
|
|
|
+ configParam.put("sheng_xiao_biao_z", "Y");
|
|
|
List<Map<String, Object>> configList = updateDataTableDao.selectDataTable("t_gdpbpzb", configParam, null);
|
|
|
if (BeanUtils.isEmpty(configList)) {
|
|
|
- throw new Exception("考勤配置不存在");
|
|
|
+ throw new Exception("考勤配置不存在或者已经失效");
|
|
|
}
|
|
|
if (configList.size() > 1) {
|
|
|
throw new Exception("考勤配置存在多条记录,请联系管理员");
|
|
|
}
|
|
|
Map<String, Object> configMap = configList.get(0);
|
|
|
- if ("N".equals(configMap.get("sheng_xiao_biao_z"))) {
|
|
|
- throw new Exception("考勤配置已失效");
|
|
|
- }
|
|
|
// 获取配置字段
|
|
|
String shangBanShiJianPz = (String) configMap.get("shang_ban_shi_jia");
|
|
|
String xiaBanShiJianPz = (String) configMap.get("xia_ban_shi_jian_");
|
|
|
@@ -250,13 +254,23 @@ public class AttendanceDetail extends AbstractDomain<String, AttendanceDetailPo>
|
|
|
LocalDateTime now = LocalDateTime.now();
|
|
|
LocalDate today = now.toLocalDate();
|
|
|
LocalDate banCiDate = today; // 默认
|
|
|
+ if(isMendCard){//补卡日期取上送日期
|
|
|
+ banCiDate = LocalDate.parse(po.getRiQi(),DATE_FORMATTER);
|
|
|
+ }
|
|
|
|
|
|
//和业务沟通后确认,跨日下班打卡只打上一日的下班卡
|
|
|
- if (!isMorningClock && "Y".equals(shiFouKuaRiPz)) {
|
|
|
+ if (isClockOut && "Y".equals(shiFouKuaRiPz)) {
|
|
|
banCiDate = today.minusDays(1);
|
|
|
}
|
|
|
- String riQi = banCiDate.format(DATE_FORMATTER);
|
|
|
- po.setRiQi(riQi);
|
|
|
+ String ClockRiQi = banCiDate.format(DATE_FORMATTER);
|
|
|
+ String riQi = "";
|
|
|
+ if(!isMendCard){//上班卡和下班卡为当前日期
|
|
|
+ po.setRiQi(ClockRiQi);
|
|
|
+ riQi = ClockRiQi;
|
|
|
+ }else{
|
|
|
+ //补卡日期为系统上送日期
|
|
|
+ riQi = po.getRiQi();
|
|
|
+ }
|
|
|
po.setShiFouKuaRi(shiFouKuaRiPz);
|
|
|
|
|
|
// 构建班次开始时间
|
|
|
@@ -287,8 +301,8 @@ public class AttendanceDetail extends AbstractDomain<String, AttendanceDetailPo>
|
|
|
diDian = result.getData().get(0).getPath().split(StringPool.BACK_SLASH + StringPool.DOT)[1];
|
|
|
po.setDiDian(diDian);
|
|
|
}catch (Exception ex){
|
|
|
- LOGGER.error("Can't get didian information",ex);
|
|
|
- return null;
|
|
|
+ diDian = result.getData().get(0).getPath().split(StringPool.BACK_SLASH+StringPool.DOT)[0];
|
|
|
+ po.setDiDian(diDian);
|
|
|
}
|
|
|
// 5. 查询当天是否存在考勤记录
|
|
|
Map<String, String> recordParam = new HashMap<>();
|
|
|
@@ -304,6 +318,9 @@ public class AttendanceDetail extends AbstractDomain<String, AttendanceDetailPo>
|
|
|
if (recordList.size() > 1) {
|
|
|
throw new Exception("考勤记录存在多条,请联系管理员排查");
|
|
|
}
|
|
|
+ if(isMendCard){
|
|
|
+ throw new Exception("用户{"+userMap.get("NAME_")+"}在"+riQi+"日已经打卡,无需补卡");
|
|
|
+ }
|
|
|
// 更新对象,存储了用户当日打卡记录id
|
|
|
existingPo = new AttendanceDetailPo();
|
|
|
Map<String, Object> recordMap = recordList.get(0);
|
|
|
@@ -368,7 +385,7 @@ public class AttendanceDetail extends AbstractDomain<String, AttendanceDetailPo>
|
|
|
po.setKaoQinZhuangTa("异常");
|
|
|
}
|
|
|
po.setDaKaCiShu(1L);
|
|
|
- } else {
|
|
|
+ } else if (isClockOut){
|
|
|
// 下班打卡
|
|
|
|
|
|
if (existingPo != null) {
|
|
|
@@ -414,6 +431,90 @@ public class AttendanceDetail extends AbstractDomain<String, AttendanceDetailPo>
|
|
|
po.setKaoQinZhuangTa("异常");
|
|
|
//po中已经设置了班次信息,直接返回
|
|
|
}
|
|
|
+ }else if(isMendCard){
|
|
|
+
|
|
|
+ // 1. 获取用户传入的补卡时间
|
|
|
+ String inputDaKa1Str = po.getDaKaShiJian1();
|
|
|
+ String inputDaKa2Str = po.getDaKaShiJian2();
|
|
|
+
|
|
|
+ // 2. 解析配置好的班次时间
|
|
|
+ LocalDateTime configStart = banCiKaiShi; //配置的班次开始时间
|
|
|
+ LocalDateTime configEnd = banCiJieShu; // 配置的班次结束时间
|
|
|
+
|
|
|
+ LocalDateTime inputDaKa1Time;
|
|
|
+ try {
|
|
|
+ inputDaKa1Time = LocalDateTime.parse(inputDaKa1Str, DATETIME_FORMATTER);
|
|
|
+ } catch (DateTimeParseException e) {
|
|
|
+ throw new Exception("上班打卡时间格式错误,应为 yyyy-MM-dd HH:mm:ss");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3.1 校验:上班打卡时间不能晚于配置的上班时间
|
|
|
+ if (inputDaKa1Time.isAfter(configStart)) {
|
|
|
+ // 上班卡日期必须与考勤记录日期(recordDate)为同一日
|
|
|
+ LocalDate recordDate = LocalDate.parse(po.getRiQi(), DATE_FORMATTER);
|
|
|
+ if (!inputDaKa1Time.toLocalDate().isEqual(recordDate)){
|
|
|
+ throw new Exception("上班打卡日期必须为考勤记录当天(" + riQi + ")");
|
|
|
+ }
|
|
|
+ throw new Exception("上班卡不允许修改比配置的上班时间{" + configStart.format(DATETIME_FORMATTER) + "}晚");
|
|
|
+ } else {
|
|
|
+ // 准时逻辑:状态正常,迟到时长为0
|
|
|
+ po.setDaKaShiJian1(inputDaKa1Str);
|
|
|
+ po.setZhuangTai1("正常");
|
|
|
+ po.setChiDaoShiChang(0L);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 处理下班打卡时间 (inputDaKa2Str 格式应为 yyyy-MM-dd HH:mm)
|
|
|
+ if (BeanUtils.isEmpty(inputDaKa2Str)) {
|
|
|
+ throw new Exception("补卡下班时间不能为空");
|
|
|
+ }
|
|
|
+ LocalDateTime inputDaKa2Time;
|
|
|
+ try {
|
|
|
+ inputDaKa2Time = LocalDateTime.parse(inputDaKa2Str, DATETIME_FORMATTER);
|
|
|
+ } catch (DateTimeParseException e) {
|
|
|
+ throw new Exception("下班打卡时间格式错误,应为 yyyy-MM-dd HH:mm");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4.1 校验:下班打卡时间不能早于配置的下班时间
|
|
|
+ if (inputDaKa2Time.isBefore(configEnd)) {
|
|
|
+ throw new Exception("下班卡不允许修改比配置的下班时间{" + configEnd.format(DATETIME_FORMATTER) + "}早");
|
|
|
+ } else {
|
|
|
+ if (!inputDaKa2Time.toLocalDate().isEqual(endDate)){
|
|
|
+ throw new Exception("下班打卡日期必须为考勤记录配置日期(" + endDate.format(DATE_FORMATTER) + ")");
|
|
|
+ }
|
|
|
+ // 正常/加班逻辑:状态正常,计算加班分钟数
|
|
|
+ po.setDaKaShiJian2(inputDaKa2Str);
|
|
|
+ po.setZhuangTai2("正常");
|
|
|
+ long jiaBanFen = Duration.between(configEnd, inputDaKa2Time).toMinutes();
|
|
|
+ po.setJiaBanShiChang(jiaBanFen);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 计算实际工作时长 (分钟)
|
|
|
+ // 校验下班不能早于上班
|
|
|
+ if (inputDaKa2Time.isBefore(inputDaKa1Time)) {
|
|
|
+ throw new Exception("补卡失败:下班打卡时间不能早于上班打卡时间");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验上下班间隔不能超过24小时,日期校验前面已经写了,资本家真该死啊
|
|
|
+ long hoursDiff = Duration.between(inputDaKa1Time, inputDaKa2Time).toHours();
|
|
|
+ if (hoursDiff >= 24) {
|
|
|
+ throw new Exception("补卡失败:上下班打卡时间间隔不能超过24小时");
|
|
|
+ }
|
|
|
+
|
|
|
+ long workMinutes = Duration.between(inputDaKa1Time, inputDaKa2Time).toMinutes();
|
|
|
+ po.setGongZuoShiChan(workMinutes);
|
|
|
+
|
|
|
+ // 6. 设置考勤总状态
|
|
|
+ if ("正常".equals(po.getZhuangTai1()) && "正常".equals(po.getZhuangTai2())) {
|
|
|
+ po.setKaoQinZhuangTa("正常");
|
|
|
+ } else {
|
|
|
+ po.setKaoQinZhuangTa("异常");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 7. 设置打卡次数 (补卡视为一次打卡操作,代表这条记录被补全了)
|
|
|
+ po.setDaKaCiShu(1L);
|
|
|
+
|
|
|
+ }else{
|
|
|
+
|
|
|
}
|
|
|
return po;
|
|
|
}
|
|
|
@@ -615,42 +716,355 @@ public class AttendanceDetail extends AbstractDomain<String, AttendanceDetailPo>
|
|
|
return records;
|
|
|
}
|
|
|
/**
|
|
|
+ * 业务场景:下班卡没打卡,考勤状态为空
|
|
|
* 修正所有满足条件的考勤记录(基于每条记录的 shi_fou_kua_ri_ 和 ri_qi_)
|
|
|
* 非跨日记录:修正 ri_qi_ = 昨天
|
|
|
* 跨日记录:修正 ri_qi_ = 前天
|
|
|
* 利用 huan_cun_biao_zhi 防重,仅处理未标记的记录
|
|
|
*/
|
|
|
public void correctRecentAttendanceOnClock() {
|
|
|
- // 修正非跨日:ri_qi_ 在 [CURDATE()-3, CURDATE()-1]
|
|
|
- String sqlNonCross = "UPDATE t_attendance_detail " +
|
|
|
- "SET kao_qin_zhuang_ta = CASE " +
|
|
|
- " WHEN zhuang_tai_1_ = '正常' AND zhuang_tai_2_ = '正常' THEN '正常' " +
|
|
|
- " ELSE '异常' END, " +
|
|
|
- " huan_cun_biao_zhi = 'Y', " +
|
|
|
- " update_time_ = NOW() " +
|
|
|
- "WHERE shi_fou_kua_ri_ = 'N' " +
|
|
|
- " AND ri_qi_ BETWEEN DATE_SUB(CURDATE(), INTERVAL 3 DAY) AND DATE_SUB(CURDATE(), INTERVAL 1 DAY) " +
|
|
|
- " AND (huan_cun_biao_zhi IS NULL OR huan_cun_biao_zhi != 'Y')";
|
|
|
+ // 1. 在 Java 中提前计算好日期范围字符串,避免数据库隐式类型转换导致索引失效
|
|
|
+ LocalDate today = LocalDate.now();
|
|
|
+ // 非跨日:需要修正 [今天-3天, 今天-1天] 的数据
|
|
|
+ String nonCrossStartDate = today.minusDays(3).format(DATE_FORMATTER);
|
|
|
+ String nonCrossEndDate = today.minusDays(1).format(DATE_FORMATTER);
|
|
|
+
|
|
|
+ // 跨日:需要修正 [今天-4天, 今天-2天] 的数据
|
|
|
+ String crossStartDate = today.minusDays(4).format(DATE_FORMATTER);
|
|
|
+ String crossEndDate = today.minusDays(2).format(DATE_FORMATTER);
|
|
|
|
|
|
- // 修正跨日:ri_qi_ 在 [CURDATE()-4, CURDATE()-2]
|
|
|
- String sqlCross = "UPDATE t_attendance_detail " +
|
|
|
+ // 2. 提取公共的 SQL 逻辑,使用 ? 占位符进行参数化查询
|
|
|
+ String baseSql = "UPDATE t_attendance_detail " +
|
|
|
"SET kao_qin_zhuang_ta = CASE " +
|
|
|
" WHEN zhuang_tai_1_ = '正常' AND zhuang_tai_2_ = '正常' THEN '正常' " +
|
|
|
" ELSE '异常' END, " +
|
|
|
" huan_cun_biao_zhi = 'Y', " +
|
|
|
" update_time_ = NOW() " +
|
|
|
- "WHERE shi_fou_kua_ri_ = 'Y' " +
|
|
|
- " AND ri_qi_ BETWEEN DATE_SUB(CURDATE(), INTERVAL 4 DAY) AND DATE_SUB(CURDATE(), INTERVAL 2 DAY) " +
|
|
|
+ "WHERE shi_fou_kua_ri_ = ? " +
|
|
|
+ " AND ri_qi_ >= ? " +
|
|
|
+ " AND ri_qi_ <= ? " +
|
|
|
" AND (huan_cun_biao_zhi IS NULL OR huan_cun_biao_zhi != 'Y')";
|
|
|
|
|
|
try {
|
|
|
- int updated1 = jdbcTemplate.update(sqlNonCross);
|
|
|
- int updated2 = jdbcTemplate.update(sqlCross);
|
|
|
+ // 3. 执行非跨日数据的修正
|
|
|
+ int updated1 = jdbcTemplate.update(baseSql, "N", nonCrossStartDate, nonCrossEndDate);
|
|
|
+ // 4. 执行跨日数据的修正
|
|
|
+ int updated2 = jdbcTemplate.update(baseSql, "Y", crossStartDate, crossEndDate);
|
|
|
if (updated1 + updated2 > 0) {
|
|
|
- LOGGER.info("打卡触发修正最近3天考勤状态,非跨日更新={}, 跨日更新={}", updated1, updated2);
|
|
|
+ LOGGER.info("打卡触发修正最近考勤状态,非跨日更新={}, 跨日更新={}", updated1, updated2);
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
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;
|
|
|
+ }
|
|
|
+
|
|
|
+ public void validateInfo(AttendanceDetailPo attendanceDetailPo) throws Exception {
|
|
|
+ if (attendanceDetailPo == null) {
|
|
|
+ throw new Exception("参数不能为空");
|
|
|
+ }
|
|
|
+ if (BeanUtils.isEmpty(attendanceDetailPo.getDaKaShiJian1())) {
|
|
|
+ throw new Exception("补卡上班时间不能为空");
|
|
|
+ }
|
|
|
+ if (BeanUtils.isEmpty(attendanceDetailPo.getDaKaShiJian2())) {
|
|
|
+ throw new Exception("补卡下班时间不能为空");
|
|
|
+ }
|
|
|
+ if (BeanUtils.isEmpty(attendanceDetailPo.getRiQi())) {
|
|
|
+ throw new Exception("补卡日期不能为空");
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ LocalDate.parse(attendanceDetailPo.getRiQi(), DATE_FORMATTER);
|
|
|
+ } catch (DateTimeParseException e) {
|
|
|
+ throw new Exception("日期格式错误,应为 yyyy-MM-dd");
|
|
|
+ }
|
|
|
+ }
|
|
|
}
|