package com.punchsettle.server.service.manager.impl; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalTime; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import com.punchsettle.server.atomic.StatPiTask; import com.punchsettle.server.atomic.entity.PiStatus; import com.punchsettle.server.atomic.entity.PiTask; import com.punchsettle.server.atomic.entity.PiTaskExt; import com.punchsettle.server.atomic.entity.PiTaskHistory; import com.punchsettle.server.atomic.entity.SettleTaskRelaHistory; import com.punchsettle.server.atomic.service.IPiStatusService; import com.punchsettle.server.atomic.service.IPiTaskExtService; import com.punchsettle.server.atomic.service.IPiTaskHistoryService; import com.punchsettle.server.atomic.service.IPiTaskService; import com.punchsettle.server.atomic.service.ISettleTaskRelaHistoryService; import com.punchsettle.server.atomic.service.IStatPiTaskMonthService; import com.punchsettle.server.atomic.service.IStatPiTaskYearService; import com.punchsettle.server.common.constant.CommonEnableStatusEnum; import com.punchsettle.server.common.exception.BusinessException; import com.punchsettle.server.common.utils.Assert; import com.punchsettle.server.constant.ArchiveStatusEnum; import com.punchsettle.server.constant.CacheNameConstant; import com.punchsettle.server.constant.ContinueStatusEnum; import com.punchsettle.server.constant.PunchInDimensionEnum; import com.punchsettle.server.constant.PunchInExtraMethodEnum; import com.punchsettle.server.constant.PunchInMethodEnum; import com.punchsettle.server.constant.PunchInResultEnum; import com.punchsettle.server.constant.PunchInResultViewEnum; import com.punchsettle.server.constant.RepeatCategoryEnum; import com.punchsettle.server.constant.SettleResultEnum; import com.punchsettle.server.constant.StatPeriodEnum; import com.punchsettle.server.constant.VersionStatusEnum; import com.punchsettle.server.pojo.punchIn.PiTaskExtDto; import com.punchsettle.server.pojo.punchIn.PiTaskExtQuery; import com.punchsettle.server.pojo.punchIn.PiTaskHistoryQuery; import com.punchsettle.server.pojo.punchIn.PiTaskHistorySimpleVO; import com.punchsettle.server.pojo.punchIn.PiTaskHistoryVO; import com.punchsettle.server.pojo.punchIn.PiTaskQuery; import com.punchsettle.server.pojo.punchIn.PiTaskRequest; import com.punchsettle.server.pojo.punchIn.PiTaskSimpleVO; import com.punchsettle.server.pojo.punchIn.PiTaskStatQuery; import com.punchsettle.server.pojo.punchIn.PiTaskStatVO; import com.punchsettle.server.pojo.punchIn.PiTaskToDoVO; import com.punchsettle.server.pojo.punchIn.PiTaskVO; import com.punchsettle.server.pojo.punchIn.PunchInHistoryVO; import com.punchsettle.server.pojo.punchIn.PunchInRequest; import com.punchsettle.server.pojo.settle.SettleTaskRelaHistoryQuery; import com.punchsettle.server.pojo.stat.StatPiTaskQuery; import com.punchsettle.server.pojo.ucharts.LineSeriesVO; import com.punchsettle.server.pojo.ucharts.LineVO; import com.punchsettle.server.pojo.uniapp.CalendarSelectedVO; import com.punchsettle.server.service.manager.ICacheManager; import com.punchsettle.server.service.manager.ICalendarManager; import com.punchsettle.server.service.manager.IPunchInCoreManager; import com.punchsettle.server.service.manager.IPunchInManager; import com.punchsettle.server.utiis.DateUtils; import com.punchsettle.server.utiis.SpringUtils; import com.punchsettle.server.utiis.UserUtils; import lombok.extern.slf4j.Slf4j; /** * @author tyuio * @version 1.0.0 * @description 打卡结算任务 服务类 * @date 2024/11/25 15:12 */ @Slf4j @Service public class PunchInManagerImpl implements IPunchInManager { @Autowired private IPiTaskService piTaskService; @Autowired private IPiTaskHistoryService piTaskHistoryService; @Autowired private IPunchInCoreManager punchInCoreManager; @Autowired private IStatPiTaskYearService statPiTaskYearService; @Autowired private IStatPiTaskMonthService statPiTaskMonthService; @Autowired private ICalendarManager calendarManager; @Autowired private ICacheManager cacheManager; @Autowired private ISettleTaskRelaHistoryService settleTaskRelaHistoryService; @Autowired private IPiTaskExtService piTaskExtService; /** * 默认时间00:00:00.000 */ private static final LocalTime ZERO_TIME = LocalTime.parse("00:00:00.000"); /** * 数值60 */ private static final BigDecimal SIXTY = new BigDecimal("60"); @Override public List queryToDoList(Long currentUserId) { Assert.isNull(currentUserId); // 查询打卡任务 List piTasks = SpringUtils.getBean(IPunchInManager.class).getTaskListInCache(currentUserId); if (CollectionUtils.isEmpty(piTasks)) { return List.of(); } // 当前日期 LocalDate today = LocalDate.now(); String todayStr = DateUtils.YYYY_MM_DD_FORMATTER.format(today); // 当前时间 LocalTime nowTime = LocalTime.now(); // 当前是否节假日 boolean holidayFlag = calendarManager.judgeHoliday(todayStr); // 查询打卡记录 List piTaskHistories = SpringUtils.getBean(IPunchInManager.class).getTaskHistoryListInCache(currentUserId, todayStr); // 打卡任务唯一ID-打卡记录关联 Map piTaskHistoryMap = piTaskHistories.stream().collect(Collectors.toMap(PiTaskHistory::getTaskUniqueId, Function.identity(), (key1, key2) -> key1)); // 1.判断重复频率,只显示今天待打卡的任务 // 2.判断显示时间,还没到点显示的暂时不显示 // 3.根据displayOrder进行排序,按自然顺序排列 return piTasks.stream().filter(piTask -> punchInCoreManager.judgeRepeat(piTask, today)) .filter(piTask -> { LocalTime displayTime = Optional.ofNullable(piTask.getDisplayTime()).orElse(ZERO_TIME); return nowTime.compareTo(displayTime) > -1; }) .sorted(Comparator.comparing(piTask -> Optional.ofNullable(piTask.getDisplayOrder()).orElse(0))) .map(piTask -> { PiTaskToDoVO piTaskToDoVO = new PiTaskToDoVO(); BeanUtils.copyProperties(piTask, piTaskToDoVO); piTaskToDoVO.setPunchInResult(PunchInResultEnum.UNDONE); piTaskToDoVO.setHolidayFlag(CommonEnableStatusEnum.ENABLED.equals(piTask.getHolidayStatus()) && holidayFlag); // 获取并设置已打卡记录 PiTaskHistory piTaskHistory = piTaskHistoryMap.get(piTask.getUniqueId()); if (Objects.nonNull(piTaskHistory)) { piTaskToDoVO.setPunchInResult(piTaskHistory.getPunchInResult()); piTaskToDoVO.setCurrentCountTrack(piTaskHistory.getCountTrack()); piTaskToDoVO.setCurrentTimeTrack(piTaskHistory.getTimeTrack()); } return piTaskToDoVO; }).collect(Collectors.toList()); } @Override @Cacheable(cacheNames = CacheNameConstant.TASK_LIST_VO, key = "T(com.punchsettle.server.utiis.UserUtils).getCurrentUserId()") public List queryTaskList() { Long currentUserId = UserUtils.getCurrentUserId(); // 查询打卡任务 List piTasks = piTaskService.getActiveTask(Arrays.asList(currentUserId)); if (CollectionUtils.isEmpty(piTasks)) { return List.of(); } // 当前日期 LocalDate today = LocalDate.now(); LocalDate beforeToady = today.minusDays(5); LocalDate afterToady = today.plusDays(4); // 获取日期范围 List weeklyDateRange = DateUtils.getDateRange(beforeToady, afterToady); // 打卡任务唯一ID Set taskUniqueIds = piTasks.stream().map(PiTask::getUniqueId).collect(Collectors.toSet()); // 找出范围内的打卡记录 PiTaskHistoryQuery piTaskHistoryQuery = new PiTaskHistoryQuery(); piTaskHistoryQuery.setTaskUniqueIds(taskUniqueIds); piTaskHistoryQuery.setUserIds(Arrays.asList(currentUserId)); piTaskHistoryQuery.setPunchInDateFrom(weeklyDateRange.getFirst().toString()); piTaskHistoryQuery.setPunchInDateTo(weeklyDateRange.getLast().toString()); List piTaskHistories = piTaskHistoryService.queryByCondition(piTaskHistoryQuery); // 打卡任务唯一ID-打卡记录关联 Map> piTaskHistoryMap = piTaskHistories.stream().collect(Collectors.groupingBy(PiTaskHistory::getTaskUniqueId)); // 按顺序排列 return piTasks.stream() .sorted(Comparator.comparing(piTask -> Optional.ofNullable(piTask.getDisplayOrder()).orElse(0))) .map(piTask -> { PiTaskSimpleVO piTaskSimpleVO = new PiTaskSimpleVO(); BeanUtils.copyProperties(piTask, piTaskSimpleVO); // 获取对应的打卡记录 List tempPiTaskHistories = Optional.ofNullable(piTaskHistoryMap.get(piTask.getUniqueId())).orElse(List.of()); // 任务创建日期 LocalDate creationDate = piTask.getCreationTime().toLocalDateTime().toLocalDate(); // 打卡日期-打卡记录关联 Map tempPiTaskHistoryMap = tempPiTaskHistories.stream().collect(Collectors.toMap(PiTaskHistory::getPunchInDate, Function.identity(), (key1, key2) -> key1)); // 按时间范围遍历,构造页面打卡结果 List piTaskHistorySimpleVOS = weeklyDateRange.stream().map(punchInDate -> { // 打卡日期 String punchInDateStr = punchInDate.toString(); // 判断页面打卡结果 PiTaskHistory piTaskHistory = tempPiTaskHistoryMap.get(punchInDateStr); PunchInResultViewEnum punchInResultView = judgePunchInResultView(piTask, piTaskHistory, punchInDate, today, creationDate); // 构建页面打卡结果 PiTaskHistorySimpleVO piTaskHistorySimpleVO = new PiTaskHistorySimpleVO(); piTaskHistorySimpleVO.setPunchInDate(punchInDateStr); piTaskHistorySimpleVO.setPunchInResult(punchInResultView); return piTaskHistorySimpleVO; }).collect(Collectors.toList()); piTaskSimpleVO.setPiTaskHistorySimpleVOS(piTaskHistorySimpleVOS); return piTaskSimpleVO; }).collect(Collectors.toList()); } /** * 判断页面打卡结果 * @return */ private PunchInResultViewEnum judgePunchInResultView(PiTask piTask, PiTaskHistory piTaskHistory, LocalDate punchInDate, LocalDate today, LocalDate creationDate) { // 未到打卡时间 if (punchInDate.isAfter(today)) { return PunchInResultViewEnum.FUTURE; } // 任务未创建,不需要打卡 if (punchInDate.isBefore(creationDate)) { return PunchInResultViewEnum.NOT_NEED; } // 打卡日期在今天之前且存在打卡记录,则简单判断打卡结果即可 if (punchInDate.isBefore(today) && Objects.nonNull(piTaskHistory)) { return PunchInResultEnum.DONE.equals(piTaskHistory.getPunchInResult()) ? PunchInResultViewEnum.DONE : PunchInResultViewEnum.UNDONE; } // 打卡日期在今天之前且不存在打卡记录,则有两种情况:1.那天不用打卡;2.那天需要打卡但是没打卡 // TODO 这里应该要有打卡任务的每天记录才能精准判断,现在先简化一下用当前任务的重复频率进行判断 if (punchInDate.isBefore(today) && Objects.isNull(piTaskHistory)) { // 判断是否需要打卡 boolean repeatResult = punchInCoreManager.judgeRepeat(piTask, today); return repeatResult ? PunchInResultViewEnum.UNDONE : PunchInResultViewEnum.NOT_NEED; } // 打卡日期是今天,且不存在打卡记录,则有两种情况:1.今天不用打卡;2.今天需要打卡但是还没打卡 if (punchInDate.isEqual(today)) { // 判断是否需要打卡 boolean repeatResult = punchInCoreManager.judgeRepeat(piTask, today); // 今天不用无须打卡 if (!repeatResult) { return PunchInResultViewEnum.NOT_NEED; } // 今天还没有打卡记录无法判断打卡结果 if (Objects.isNull(piTaskHistory)) { return PunchInResultViewEnum.TODAY_UNKNOWN; } return PunchInResultEnum.DONE.equals(piTaskHistory.getPunchInResult()) ? PunchInResultViewEnum.DONE : PunchInResultViewEnum.TODAY_UNKNOWN; } // 不符合任何情况则是无需打卡 return PunchInResultViewEnum.NOT_NEED; } @Override public PiTaskVO queryTask(Long id) { Assert.isNullInBusiness(id, "请传入待查询的任务ID"); PiTaskVO piTaskVO = new PiTaskVO(); // 获取打卡任务 PiTask piTask = Optional.ofNullable(piTaskService.getById(id)).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务")); BeanUtils.copyProperties(piTask, piTaskVO); if (RepeatCategoryEnum.CUSTOM.equals(piTask.getRepeatCategory()) && StringUtils.hasText(piTask.getRepeatCustomDay())) { piTaskVO.setRepeatCustomDayList(Arrays.stream(piTask.getRepeatCustomDay().split(",")).toList()); } // 获取拓展信息 PiTaskExtQuery piTaskExtQuery = new PiTaskExtQuery(); piTaskExtQuery.setPunchInTaskIds(Arrays.asList(piTask.getId())); List piTaskExtList = piTaskExtService.queryByCondition(piTaskExtQuery); // 没有拓展信息直接返回 if (CollectionUtils.isEmpty(piTaskExtList)) { return piTaskVO; } // 拓展信息(当天打卡) List taskExtList = piTaskExtList.stream().filter(piTaskExt -> PunchInDimensionEnum.ONE_DAY.equals(piTaskExt.getDimension())).map(piTaskExt -> { PiTaskExtDto piTaskExtDto = new PiTaskExtDto(); BeanUtils.copyProperties(piTaskExt, piTaskExtDto); return piTaskExtDto; }).toList(); piTaskVO.setTaskExtList(taskExtList); // 拓展信息(连续打卡) List continueTaskExtList = piTaskExtList.stream().filter(piTaskExt -> PunchInDimensionEnum.MULTI_DAY.equals(piTaskExt.getDimension())).map(piTaskExt -> { PiTaskExtDto piTaskExtDto = new PiTaskExtDto(); BeanUtils.copyProperties(piTaskExt, piTaskExtDto); return piTaskExtDto; }).toList(); piTaskVO.setContinueTaskExtList(continueTaskExtList); return piTaskVO; } @Override @Transactional(rollbackFor = Exception.class) public void saveTask(PiTaskRequest request) { Assert.isNullInBusiness(request, "请传入任务信息"); // 今天 LocalDate today = LocalDate.now(); // 当前用户ID Long currentUserId = UserUtils.getCurrentUserId(); // 数据校验 if (PunchInMethodEnum.TIMING.equals(request.getPunchInMethod())) { if (Objects.isNull(request.getCompareRule()) || Objects.isNull(request.getTimeTrack())) { throw BusinessException.fail("打卡类型:计时,比较规则和时间不能为空"); } if (CommonEnableStatusEnum.ENABLED.equals(request.getHolidayStatus()) && Objects.isNull(request.getHolidayTimeTrack())) { throw BusinessException.fail("打卡类型:计时,启用了节假日奖励,节假日的时间不能为空"); } } if (PunchInMethodEnum.COUNT.equals(request.getPunchInMethod())) { if (Objects.isNull(request.getCompareRule()) || Objects.isNull(request.getCountTrack())) { throw BusinessException.fail("打卡类型:计数,比较规则和次数不能为空"); } if (CommonEnableStatusEnum.ENABLED.equals(request.getHolidayStatus()) && Objects.isNull(request.getHolidayCountTrack())) { throw BusinessException.fail("打卡类型:计数,启用了节假日奖励,节假日的次数不能为空"); } } if (CommonEnableStatusEnum.ENABLED.equals(request.getFullAttendanceStatus()) && (Objects.isNull(request.getFullAttendancePeriod()) || Objects.isNull(request.getFullAttendanceFaultToleranceCnt()))) { throw BusinessException.fail("启用了全勤奖励,全勤周期和全勤容错次数不能为空"); } if (RepeatCategoryEnum.CUSTOM.equals(request.getRepeatCategory()) && CollectionUtils.isEmpty(request.getRepeatCustomDayList())) { throw BusinessException.fail("自定义重复周期,自定义重复日不能为空"); } if (PunchInExtraMethodEnum.FIXED.equals(request.getExtraMethod()) || PunchInExtraMethodEnum.INTERVAL.equals(request.getExtraMethod())) { if (PunchInMethodEnum.TIMING.equals(request.getPunchInMethod()) && Objects.isNull(request.getExtraTimeStep())) { throw BusinessException.fail("打卡类型:计时,额外奖励方式为固定或区间,额外奖励时间间隔不能为空"); } if (PunchInExtraMethodEnum.FIXED.equals(request.getExtraMethod()) && Objects.isNull(request.getExtraPoints())) { throw BusinessException.fail("额外奖励方式为固定,额外奖励积分不能为空"); } if (PunchInExtraMethodEnum.INTERVAL.equals(request.getExtraMethod())) { if (CollectionUtils.isEmpty(request.getTaskExtList())) { throw BusinessException.fail("额外奖励方式为区间,积分区间信息不能为空"); } Map> initialValueMap = request.getTaskExtList().stream().collect(Collectors.groupingBy(PiTaskExtDto::getInitialValue)); for (List value : initialValueMap.values()) { if (value.size() > 1) { throw BusinessException.fail("任务积分区间不能有重复的起始值"); } } } } if (CommonEnableStatusEnum.ENABLED.equals(request.getContinueStatus()) && (Objects.isNull(request.getGraceDay()) || Objects.isNull(request.getContinueInterruptedCount()) || Objects.isNull(request.getPenaltyDay()))) { throw BusinessException.fail("启用了连续规则,宽限期、连续中断次数、惩罚天数不能为空"); } if (CommonEnableStatusEnum.ENABLED.equals(request.getTaskPointsStatus())) { if (CollectionUtils.isEmpty(request.getContinueTaskExtList())) { throw BusinessException.fail("启用了任务积分计算,连续任务积分区间信息不能为空"); } Map> initialValueMap = request.getContinueTaskExtList().stream().collect(Collectors.groupingBy(PiTaskExtDto::getInitialValue)); for (List value : initialValueMap.values()) { if (value.size() > 1) { throw BusinessException.fail("连续任务积分区间不能有重复的起始值"); } } } if (Objects.nonNull(request.getAutoArchiveDate()) && request.getAutoArchiveDate().isBefore(today)) { throw BusinessException.fail("任务自动归档时间无效,必需大于等于当前时间"); } // 新增打卡任务 PiTask addPiTask = new PiTask(); BeanUtils.copyProperties(request, addPiTask); addPiTask.setId(null); addPiTask.setTaskStatus(VersionStatusEnum.ACTIVE); addPiTask.setTaskVersion(0); addPiTask.setArchiveStatus(ArchiveStatusEnum.ACTIVE); if (!CollectionUtils.isEmpty(request.getRepeatCustomDayList())) { addPiTask.setRepeatCustomDay(String.join(",", request.getRepeatCustomDayList())); } // 拓展信息(当天打卡) List taskExtList = Optional.ofNullable(request.getTaskExtList()).orElse(List.of()).stream() .sorted(Comparator.comparing(PiTaskExtDto::getInitialValue)) .map(piTaskExtDto -> { PiTaskExt piTaskExt = new PiTaskExt(); BeanUtils.copyProperties(piTaskExtDto, piTaskExt); piTaskExt.setUserId(currentUserId); piTaskExt.setDimension(PunchInDimensionEnum.ONE_DAY); return piTaskExt; }).toList(); // 拓展信息(连续打卡) List continueTaskExtList = Optional.ofNullable(request.getContinueTaskExtList()).orElse(List.of()).stream() .sorted(Comparator.comparing(PiTaskExtDto::getInitialValue)) .map(piTaskExtDto -> { PiTaskExt piTaskExt = new PiTaskExt(); BeanUtils.copyProperties(piTaskExtDto, piTaskExt); piTaskExt.setUserId(currentUserId); piTaskExt.setDimension(PunchInDimensionEnum.MULTI_DAY); return piTaskExt; }).toList(); // 如果任务已存在,则更新任务版本号、设置唯一ID、旧数据归档 if (Objects.nonNull(request.getId())) { PiTask piTask = Optional.ofNullable(piTaskService.getById(request.getId())).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务")); addPiTask.setTaskVersion(piTask.getTaskVersion() + 1); addPiTask.setUniqueId(piTask.getUniqueId()); // 旧数据归档 PiTask updatePiTask = new PiTask(); updatePiTask.setId(piTask.getId()); updatePiTask.setTaskStatus(VersionStatusEnum.ARCHIVE); piTaskService.update(updatePiTask); // 切换打卡模式删除当前旧打卡数据 if (!piTask.getPunchInMethod().equals(addPiTask.getPunchInMethod())) { PiTaskHistoryQuery piTaskHistoryQuery = new PiTaskHistoryQuery(); piTaskHistoryQuery.setTaskUniqueIds(Arrays.asList(piTask.getUniqueId())); piTaskHistoryQuery.setPunchInDate(LocalDate.now().toString()); List piTaskHistories = piTaskHistoryService.queryByCondition(piTaskHistoryQuery); if (!CollectionUtils.isEmpty(piTaskHistories)) { Set piTaskHistoryIds = piTaskHistories.stream().map(PiTaskHistory::getId).collect(Collectors.toSet()); piTaskHistoryService.deleteByIds(piTaskHistoryIds); } } } // 保存数据 piTaskService.insert(addPiTask); if (!CollectionUtils.isEmpty(taskExtList)) { taskExtList.forEach(v -> v.setTaskId(addPiTask.getId())); piTaskExtService.batchAdd(taskExtList); } if (!CollectionUtils.isEmpty(continueTaskExtList)) { continueTaskExtList.forEach(v -> v.setTaskId(addPiTask.getId())); piTaskExtService.batchAdd(continueTaskExtList); } // 如果首次保存,需要设置唯一ID if (Objects.isNull(addPiTask.getUniqueId())) { PiTask updatePiTask = new PiTask(); updatePiTask.setId(addPiTask.getId()); updatePiTask.setUniqueId(addPiTask.getId()); piTaskService.update(updatePiTask); } // 不是新增打卡任务 更新打卡记录 if (Objects.nonNull(addPiTask.getUniqueId())) { // 查询今天的打卡记录 PiTaskHistory oldPiTaskHistory = piTaskHistoryService.getByTaskUniqueIdAndPunchInDate(addPiTask.getUniqueId(), today.toString()); // 已打卡则重新判断打卡状态 if (Objects.nonNull(oldPiTaskHistory)) { // 新编辑的打卡任务+旧的打卡记录判断打卡结果 PunchInResultEnum punchInResult = punchInCoreManager.judgePunchInResultInTask(addPiTask, oldPiTaskHistory); // 更新 PiTaskHistory updatePiTaskHistory = new PiTaskHistory(); updatePiTaskHistory.setId(oldPiTaskHistory.getId()); updatePiTaskHistory.setPunchInResult(punchInResult); piTaskHistoryService.update(updatePiTaskHistory); } } // 清除缓存 clearCache(currentUserId, true, true); } @Override @Transactional(rollbackFor = Exception.class) public void deleteTask(Long id) { Assert.isNullInBusiness(id, "请传入待删除的任务"); // 新增打卡任务记录 PiTask addPiTask = copyAndArchivePiTask(id); addPiTask.setTaskStatus(VersionStatusEnum.DELETE); piTaskService.insert(addPiTask); // 清除缓存 clearCache(UserUtils.getCurrentUserId(), false, false); } @Override @Transactional(rollbackFor = Exception.class) public void doPunchIn(PunchInRequest request) { if (Objects.isNull(request) || Objects.isNull(request.getId())) { BusinessException.throwFail("请传入待打卡的任务"); } // 当前用户ID Long currentUserId = UserUtils.getCurrentUserId(); PiTask piTask = Optional.ofNullable(piTaskService.getById(request.getId())).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务")); if (PunchInMethodEnum.TIMING.equals(piTask.getPunchInMethod()) && Objects.isNull(request.getTimeTrack())) { BusinessException.throwFail("打卡类型:计时,请传入时间记录"); } // 打卡日期 String punchInDate = LocalDate.now().toString(); // 查询今天的打卡记录 PiTaskHistory oldPiTaskHistory = piTaskHistoryService.getByTaskUniqueIdAndPunchInDate(piTask.getUniqueId(), punchInDate); // 待更新/新增的打卡记录 PiTaskHistory tempPiTaskHistory = new PiTaskHistory(); tempPiTaskHistory.setTaskId(piTask.getId()); // 不存在则新增,存在则更新 if (Objects.isNull(oldPiTaskHistory)) { tempPiTaskHistory.setTaskUniqueId(piTask.getUniqueId()); tempPiTaskHistory.setPunchInDate(punchInDate); tempPiTaskHistory.setUserId(currentUserId); } else { tempPiTaskHistory.setId(oldPiTaskHistory.getId()); tempPiTaskHistory.setPunchInDate(oldPiTaskHistory.getPunchInDate()); } // 打卡类型:单次打卡, if (PunchInMethodEnum.SINGLE.equals(piTask.getPunchInMethod())) { // 需要判断是否重复打卡 if (Objects.nonNull(oldPiTaskHistory) && Optional.ofNullable(oldPiTaskHistory.getCountTrack()).orElse(0) > 0) { BusinessException.throwFail("已打卡,无须重复打卡"); } // 默认打卡次数是1,是计数打卡的特列 tempPiTaskHistory.setCountTrack(1); } // 打卡类型:计数,需要累加打卡次数 if (PunchInMethodEnum.COUNT.equals(piTask.getPunchInMethod())) { if (Objects.isNull(oldPiTaskHistory)) { tempPiTaskHistory.setCountTrack(1); } else { tempPiTaskHistory.setCountTrack(Optional.ofNullable(oldPiTaskHistory.getCountTrack()).orElse(0) + 1); } } // 打卡类型:计时,需要记录最新打卡时长 if (PunchInMethodEnum.TIMING.equals(piTask.getPunchInMethod())) { tempPiTaskHistory.setTimeTrack(request.getTimeTrack()); } // 设置打卡结果 PunchInResultEnum punchInResult = punchInCoreManager.judgePunchInResultInTask(piTask, tempPiTaskHistory); tempPiTaskHistory.setPunchInResult(punchInResult); // 新增或更新 if (Objects.isNull(oldPiTaskHistory)) { piTaskHistoryService.insert(tempPiTaskHistory); } else { piTaskHistoryService.update(tempPiTaskHistory); } // 清楚缓存 clearCache(currentUserId, true, true); } @Override @Transactional(rollbackFor = Exception.class) public void archiveTask(Long id) { Assert.isNullInBusiness(id, "请传入待删除的任务"); // 归档 PiTask addPiTask = copyAndArchivePiTask(id); addPiTask.setArchiveStatus(ArchiveStatusEnum.ARCHIVE); addPiTask.setManualArchiveDate(LocalDate.now()); piTaskService.insert(addPiTask); // 清楚缓存 clearCache(UserUtils.getCurrentUserId(), false, false); } @Override public void revokePunchIn(Long id) { // 获取打卡任务 PiTask piTask = Optional.ofNullable(piTaskService.getById(id)).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务")); // 获取打卡记录 PiTaskHistory piTaskHistory = piTaskHistoryService.getByTaskUniqueIdAndPunchInDate(piTask.getUniqueId(), LocalDate.now().toString()); if (Objects.isNull(piTaskHistory)) { return; } PiTaskHistory updatePiTaskHistory = new PiTaskHistory(); updatePiTaskHistory.setId(piTaskHistory.getId()); updatePiTaskHistory.setTaskId(piTask.getId()); updatePiTaskHistory.setPunchInDate(piTaskHistory.getPunchInDate()); // 单次打卡,次数置为0 if (PunchInMethodEnum.SINGLE.equals(piTask.getPunchInMethod())) { updatePiTaskHistory.setCountTrack(0); } // 计时打卡 时间置为00:00:00.000 if (PunchInMethodEnum.TIMING.equals(piTask.getPunchInMethod())) { updatePiTaskHistory.setTimeTrack(ZERO_TIME); } // 计数打卡,当前打卡次数减1 if (PunchInMethodEnum.COUNT.equals(piTask.getPunchInMethod())) { Integer countTrack = Optional.ofNullable(piTaskHistory.getCountTrack()).orElse(0); if (countTrack == 0) { return; } updatePiTaskHistory.setCountTrack(countTrack - 1); } // 设置打卡结果 PunchInResultEnum punchInResult = punchInCoreManager.judgePunchInResultInTask(piTask, updatePiTaskHistory); updatePiTaskHistory.setPunchInResult(punchInResult); // 更新 piTaskHistoryService.update(updatePiTaskHistory); // 清楚缓存 clearCache(UserUtils.getCurrentUserId(), true, true); } @Override @Cacheable(cacheNames = CacheNameConstant.STAT_TASK, key = "T(com.punchsettle.server.utiis.UserUtils).getCurrentUserId()+'_'+#query.taskId+'_'+#query.statTime", condition = "#query.taskId != null && #query.statTime != null && !#query.statTime.isBlank()") public PiTaskStatVO queryStat(PiTaskStatQuery query) { // 当前用户ID Long currentUserId = UserUtils.getCurrentUserId(); // 查找打卡任务 PiTask piTask = Optional.ofNullable(piTaskService.getById(query.getTaskId())).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务")); // 查询统计数据 StatPiTask statPiTask = null; // 统计范围的第一天 LocalDate statFirstDate = null; // 统计范围的最后一天 LocalDate statLastDay = null; // 今天 LocalDate today = LocalDate.now(); // 构造查询条件 StatPiTaskQuery statPiTaskQuery = new StatPiTaskQuery(); statPiTaskQuery.setUserId(currentUserId); statPiTaskQuery.setTaskUniqueId(piTask.getUniqueId()); statPiTaskQuery.setStatTime(query.getStatTime()); if (StatPeriodEnum.YEAR.equals(query.getStatPeriod())) { statPiTask = statPiTaskYearService.queryOneByCondition(statPiTaskQuery); statFirstDate = LocalDate.of(Integer.valueOf(query.getStatTime()), 1, 1); statLastDay = DateUtils.getLastDayOfYear(statFirstDate); } else if (StatPeriodEnum.MONTH.equals(query.getStatPeriod())) { statPiTask = statPiTaskMonthService.queryOneByCondition(statPiTaskQuery); String[] split = query.getStatTime().split("-"); statFirstDate = LocalDate.of(Integer.valueOf(split[0]), Integer.valueOf(split[1]), 1); statLastDay = DateUtils.getLastDayOfYear(statFirstDate); } // 查询打卡记录 PiTaskHistoryQuery piTaskHistoryQuery = new PiTaskHistoryQuery(); piTaskHistoryQuery.setUserIds(Arrays.asList(currentUserId)); piTaskHistoryQuery.setPunchInDateLike(query.getStatTime()); piTaskHistoryQuery.setTaskUniqueIds(Arrays.asList(piTask.getUniqueId())); List piTaskHistories = piTaskHistoryService.queryByCondition(piTaskHistoryQuery); // 构造折线图数据 List dateRangeInMonth = null; if (today.isBefore(statLastDay)) { dateRangeInMonth = DateUtils.getDateRange(statFirstDate, today); } else { dateRangeInMonth = DateUtils.getDateRange(statFirstDate, statLastDay); } // 横坐标 String[] categories = dateRangeInMonth.stream().map(v -> DateUtils.MM_DD_FORMATTER.format(v)).toArray(String[]::new); // 打卡日期-打卡记录 关联 Map piTaskHistoryMap = piTaskHistories.stream().collect(Collectors.toMap(piTaskHistory -> piTaskHistory.getPunchInDate().substring(5), Function.identity(), (key1, key2) -> key1)); Object[] seriesData = Arrays.stream(categories).map(category -> { PiTaskHistory piTaskHistory = piTaskHistoryMap.get(category); if (Objects.isNull(piTaskHistory)) { return null; } if (PunchInMethodEnum.SINGLE.equals(piTask.getPunchInMethod())) { return PunchInResultEnum.DONE.equals(piTaskHistory.getPunchInResult()) ? 1 : 0; } if (PunchInMethodEnum.COUNT.equals(piTask.getPunchInMethod())) { return Optional.ofNullable(piTaskHistory.getCountTrack()).orElse(0); } if (PunchInMethodEnum.TIMING.equals(piTask.getPunchInMethod())) { LocalTime timeTrack = Optional.ofNullable(piTaskHistory.getTimeTrack()).orElse(ZERO_TIME); BigDecimal hourVal = BigDecimal.valueOf(timeTrack.getHour()); BigDecimal minVal = BigDecimal.valueOf(timeTrack.getMinute()).divide(SIXTY, 2, RoundingMode.HALF_UP); return hourVal.add(minVal); } return null; }).toArray(); // 构建折线图数据 LineSeriesVO lineSeriesVO = new LineSeriesVO(); lineSeriesVO.setName("每日打卡数据"); lineSeriesVO.setData(seriesData); LineVO lineVO = new LineVO(); lineVO.setCategories(categories); lineVO.setSeries(Arrays.asList(lineSeriesVO)); // 查询打卡任务 Set piTaskIds = piTaskHistories.stream().map(PiTaskHistory::getTaskId).collect(Collectors.toSet()); PiTaskQuery piTaskQuery = new PiTaskQuery(); piTaskQuery.setIds(piTaskIds); List piTasks = piTaskService.queryByCondition(piTaskQuery); // 打卡任务ID-打卡任务对象关联 Map piTaskMap = piTasks.stream().collect(Collectors.toMap(PiTask::getId, Function.identity(), (key1, key2) -> key1)); // 获取结算数据 SettleTaskRelaHistoryQuery settleTaskRelaHistoryQuery = new SettleTaskRelaHistoryQuery(); settleTaskRelaHistoryQuery.setSettleDateFrom(statFirstDate.toString()); settleTaskRelaHistoryQuery.setSettleDateTo(statLastDay.toString()); settleTaskRelaHistoryQuery.setUserIds(Arrays.asList(UserUtils.getCurrentUserId())); settleTaskRelaHistoryQuery.setSettleResults(Arrays.asList(SettleResultEnum.SETTLED, SettleResultEnum.NO_SETTLED)); settleTaskRelaHistoryQuery.setTaskUniqueId(piTask.getUniqueId()); List settleTaskRelaHistories = settleTaskRelaHistoryService.queryByCondition(settleTaskRelaHistoryQuery); // 结算日期 - 结算信息 关联 Map settleTaskRelaHistoryMap = settleTaskRelaHistories.stream().collect(Collectors.toMap(SettleTaskRelaHistory::getSettleDate, Function.identity(), (key1, key2) -> key1)); // 组装数据 List piTaskHistoryVOS = piTaskHistories.stream().map(piTaskHistory -> { PiTask tempPiTask = piTaskMap.get(piTaskHistory.getTaskId()); SettleTaskRelaHistory settleTaskRelaHistory = settleTaskRelaHistoryMap.get(piTaskHistory.getPunchInDate()); PiTaskHistoryVO piTaskHistoryVO = new PiTaskHistoryVO(); piTaskHistoryVO.setPunchInDate(piTaskHistory.getPunchInDate()); if (Objects.nonNull(tempPiTask)) { piTaskHistoryVO.setPunchInMethod(tempPiTask.getPunchInMethod()); } if (Objects.nonNull(settleTaskRelaHistory)) { piTaskHistoryVO.setSettleResult(settleTaskRelaHistory.getSettleResult()); piTaskHistoryVO.setSettlePoints(Optional.ofNullable(settleTaskRelaHistory.getSettlePoints()).orElse(0)); } piTaskHistoryVO.setPunchInResult(piTaskHistory.getPunchInResult()); piTaskHistoryVO.setCountTrack(piTaskHistory.getCountTrack()); piTaskHistoryVO.setTimeTrack(piTaskHistory.getTimeTrack()); return piTaskHistoryVO; }).collect(Collectors.toList()); PiTaskStatVO piTaskStatVO = new PiTaskStatVO(); if (Objects.nonNull(statPiTask)) { BeanUtils.copyProperties(statPiTask, piTaskStatVO); } piTaskStatVO.setPiTaskHistoryVOS(piTaskHistoryVOS); piTaskStatVO.setLineVO(lineVO); return piTaskStatVO; } /** * 复制打卡任务信息并把旧的信息归档(审计信息不复制、任务版本号自动加1,任务状态:活跃) * @param id 打卡任务id * @return */ private PiTask copyAndArchivePiTask(Long id) { // 获取旧的打卡任务信息 PiTask oldPiTask = Optional.ofNullable(piTaskService.getById(id)).orElseThrow(() -> BusinessException.fail("打卡任务不存在")); // 旧的打卡任务信息归档 PiTask updatePiTask = new PiTask(); updatePiTask.setId(oldPiTask.getId()); updatePiTask.setTaskStatus(VersionStatusEnum.ARCHIVE); piTaskService.update(updatePiTask); // 新的打卡任务信息 PiTask piTask = new PiTask(); BeanUtils.copyProperties(oldPiTask, piTask); piTask.setId(null); piTask.setTaskStatus(VersionStatusEnum.ACTIVE); piTask.setTaskVersion(Optional.ofNullable(oldPiTask.getTaskVersion()).orElse(0) + 1); return piTask; } @Override @Cacheable(cacheNames = CacheNameConstant.TASK_PUNCH_IN_HISTORY, key = "T(com.punchsettle.server.utiis.UserUtils).getCurrentUserId()+'_'+#punchInDate", condition = "#punchInDate != null && !#punchInDate.isBlank()") public PunchInHistoryVO queryPunchInHistory(String punchInDate) { Assert.isNullInBusiness(punchInDate, "打卡日期不能为空"); // 查询打卡记录 PiTaskHistoryQuery piTaskHistoryQuery = new PiTaskHistoryQuery(); piTaskHistoryQuery.setUserIds(Arrays.asList(UserUtils.getCurrentUserId())); piTaskHistoryQuery.setPunchInDateLike(punchInDate.substring(0, 7)); List piTaskHistories = piTaskHistoryService.queryByCondition(piTaskHistoryQuery); // 打卡数据直接返回 if (CollectionUtils.isEmpty(piTaskHistories)) { return new PunchInHistoryVO(); } // 打卡日期 - 打卡记录关联 Map> piTaskHistoryMap = piTaskHistories.stream().collect(Collectors.groupingBy(PiTaskHistory::getPunchInDate)); PunchInHistoryVO punchInHistoryVO = new PunchInHistoryVO(); // 日历打点信息 List calendarSelectedVOS = piTaskHistoryMap.keySet().stream().map(tempPunchInDate -> { // 获取打卡记录 List tempPiTaskHistories = piTaskHistoryMap.get(tempPunchInDate); // 打卡完成数 long punchInDoneCount = tempPiTaskHistories.stream().filter(piTaskHistory -> PunchInResultEnum.DONE.equals(piTaskHistory.getPunchInResult())).count(); CalendarSelectedVO calendarSelectedVO = new CalendarSelectedVO(); calendarSelectedVO.setDate(tempPunchInDate); calendarSelectedVO.setInfo(String.valueOf(punchInDoneCount)); return calendarSelectedVO; }).toList(); // 设置日历打点信息 punchInHistoryVO.setCalendarSelectedVOS(calendarSelectedVOS); // 获取任务信息 List tempPiTaskHistories = piTaskHistoryMap.get(punchInDate); // 当天没有直接返回 if (CollectionUtils.isEmpty(tempPiTaskHistories)) { return punchInHistoryVO; } Set taskIds = tempPiTaskHistories.stream().map(PiTaskHistory::getTaskId).collect(Collectors.toSet()); PiTaskQuery piTaskQuery = new PiTaskQuery(); piTaskQuery.setIds(taskIds); List piTasks = piTaskService.queryByCondition(piTaskQuery); // 任务ID - 任务信息 Map piTaskMap = piTasks.stream().collect(Collectors.toMap(PiTask::getId, Function.identity(), (key1, key2) -> key1)); // 获取结算数据 SettleTaskRelaHistoryQuery settleTaskRelaHistoryQuery = new SettleTaskRelaHistoryQuery(); settleTaskRelaHistoryQuery.setSettleDate(punchInDate); settleTaskRelaHistoryQuery.setUserIds(Arrays.asList(UserUtils.getCurrentUserId())); settleTaskRelaHistoryQuery.setSettleResults(Arrays.asList(SettleResultEnum.SETTLED, SettleResultEnum.NO_SETTLED)); List settleTaskRelaHistories = settleTaskRelaHistoryService.queryByCondition(settleTaskRelaHistoryQuery); // 任务唯一ID - 结算信息 关联 Map settleTaskRelaHistoryMap = settleTaskRelaHistories.stream().collect(Collectors.toMap(SettleTaskRelaHistory::getPiTaskUniqueId, Function.identity(), (key1, key2) -> key1)); // 打卡记录 List piTaskHistoryVOS = tempPiTaskHistories.stream().map(piTaskHistory -> { // 获取打卡任务 PiTask piTask = piTaskMap.get(piTaskHistory.getTaskId()); // 获取结算信息 SettleTaskRelaHistory settleTaskRelaHistory = settleTaskRelaHistoryMap.get(piTaskHistory.getTaskUniqueId()); PiTaskHistoryVO piTaskHistoryVO = new PiTaskHistoryVO(); if (Objects.nonNull(piTask)) { piTaskHistoryVO.setTaskName(piTask.getTaskName()); piTaskHistoryVO.setPunchInMethod(piTask.getPunchInMethod()); } if (Objects.nonNull(settleTaskRelaHistory)) { piTaskHistoryVO.setSettleResult(settleTaskRelaHistory.getSettleResult()); piTaskHistoryVO.setSettlePoints(settleTaskRelaHistory.getSettlePoints()); } piTaskHistoryVO.setTimeTrack(piTaskHistory.getTimeTrack()); piTaskHistoryVO.setCountTrack(piTaskHistory.getCountTrack()); piTaskHistoryVO.setPunchInResult(piTaskHistory.getPunchInResult()); return piTaskHistoryVO; }).collect(Collectors.toList()); // 设置打卡记录 punchInHistoryVO.setPiTaskHistoryVOS(piTaskHistoryVOS); return punchInHistoryVO; } @Override @Cacheable(cacheNames = CacheNameConstant.TASK_LIST_FOR_USER, key = "#userId", condition = "#userId != null") public List getTaskListInCache(Long userId) { Assert.isNullInBusiness(userId, "用户ID不能为空"); return piTaskService.getActiveTask(Arrays.asList(userId)); } @Override @Cacheable(cacheNames = CacheNameConstant.TASK_HISTORY_LIST_FOR_USER, key = "#userId+'_'+#punchInDate", condition = "#userId != null && #punchInDate != null && !#punchInDate.isBlank()") public List getTaskHistoryListInCache(Long userId, String punchInDate) { Assert.isNullInBusiness(userId, "用户ID不能为空"); Assert.isNullInBusiness(punchInDate, "打卡日期不能为空"); PiTaskHistoryQuery piTaskHistoryQuery = new PiTaskHistoryQuery(); piTaskHistoryQuery.setPunchInDate(punchInDate); piTaskHistoryQuery.setUserIds(Arrays.asList(userId)); List piTaskHistories = piTaskHistoryService.queryByCondition(piTaskHistoryQuery); return piTaskHistories; } @Override public void clearCache(Long userId, boolean deleteHistory, boolean deleteStat) { cacheManager.batchEvict(Arrays.asList(CacheNameConstant.TASK_LIST_FOR_USER, CacheNameConstant.TASK_LIST_VO), userId); if (deleteHistory) { cacheManager.batchEvictLike(Arrays.asList(CacheNameConstant.TASK_PUNCH_IN_HISTORY, CacheNameConstant.TASK_HISTORY_LIST_FOR_USER), String.valueOf(userId)); } if (deleteStat) { cacheManager.batchEvictLike(Arrays.asList(CacheNameConstant.STAT_TASK), String.valueOf(userId)); } } }