package com.punchsettle.server.service.manager.impl; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.LocalTime; import java.time.YearMonth; import java.time.temporal.ChronoUnit; import java.util.ArrayList; 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.function.Function; import java.util.stream.Collectors; import com.punchsettle.server.atomic.entity.PunchInMultiTask; import com.punchsettle.server.atomic.entity.PunchInMultiTaskExt; import com.punchsettle.server.atomic.entity.PunchInMultiTaskHistory; import com.punchsettle.server.atomic.entity.PunchInStatsMonth; import com.punchsettle.server.atomic.entity.PunchInStatsWeek; import com.punchsettle.server.atomic.entity.PunchInStatus; import com.punchsettle.server.atomic.entity.PunchInTaskExt; import com.punchsettle.server.constant.ConsecutiveStatusEnum; import com.punchsettle.server.constant.FullAttendancePeriodEnum; import com.punchsettle.server.constant.PunchInDimensionEnum; import com.punchsettle.server.constant.PunchInExtraMethodEnum; import com.punchsettle.server.constant.PunchInMethodMultiEnum; import com.punchsettle.server.constant.RepeatCategoryEnum; import com.punchsettle.server.service.manager.ICalendarManager; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import com.punchsettle.server.atomic.entity.PunchIn; import com.punchsettle.server.atomic.entity.PunchInRecord; import com.punchsettle.server.atomic.entity.PunchInTask; import com.punchsettle.server.atomic.entity.PunchInTaskHistory; import com.punchsettle.server.atomic.service.IPunchInRecordService; import com.punchsettle.server.atomic.service.IPunchInService; import com.punchsettle.server.common.exception.BusinessException; import com.punchsettle.server.common.utils.Assert; import com.punchsettle.server.common.constant.CommonEnableStatusEnum; import com.punchsettle.server.constant.CompareRuleEnum; import com.punchsettle.server.constant.PunchInCategoryEnum; import com.punchsettle.server.constant.PunchInMethodEnum; import com.punchsettle.server.constant.PunchInSettleTypeEnum; import com.punchsettle.server.constant.PunchInStatusEnum; import com.punchsettle.server.constant.PunchInStatusV1Enum; import com.punchsettle.server.constant.PunchInStatusViewEnum; import com.punchsettle.server.pojo.punchin.PunchInCalendarDataVO; import com.punchsettle.server.pojo.punchin.PunchInDataQuery; import com.punchsettle.server.pojo.punchin.PunchInDataVO; import com.punchsettle.server.pojo.punchin.PunchInQuery; import com.punchsettle.server.pojo.punchin.PunchInRecordDataVO; import com.punchsettle.server.pojo.punchin.PunchInRecordQuery; import com.punchsettle.server.pojo.punchin.PunchInRecordRequest; import com.punchsettle.server.pojo.punchin.PunchInRecordVO; import com.punchsettle.server.pojo.punchin.PunchInRequest; import com.punchsettle.server.pojo.punchin.PunchInVO; import com.punchsettle.server.pojo.punchin.PunchInWithRecordVO; import com.punchsettle.server.service.manager.IPunchInManager; import com.punchsettle.server.service.manager.ISettleManager; import com.punchsettle.server.utiis.DateUtils; import com.punchsettle.server.utiis.UserUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; /** * @author tyuio * @version 1.0.0 * @description 打卡结算任务 服务类 * @date 2024/11/25 15:12 */ @Slf4j @Service public class PunchInManagerImpl implements IPunchInManager { @Autowired private IPunchInService punchInService; @Autowired private IPunchInRecordService punchInRecordService; @Autowired private ISettleManager settleManager; /** * 数值100 */ private static final BigDecimal ONE_HUNDRED = new BigDecimal(100); @Override public List queryPunchInAndRecord() { // 获取当前用户ID Long currentUserId = Optional.ofNullable(UserUtils.getCurrentUserId()) .orElseThrow(() -> BusinessException.fail("无法获取当前用户信息,无法查询打卡任务")); // 查询打卡任务 PunchInQuery punchInQuery = new PunchInQuery(); punchInQuery.setArchiveFlag(false); punchInQuery.setUserIds(Arrays.asList(currentUserId)); List punchIns = punchInService.listByCondition(punchInQuery); if (CollectionUtils.isEmpty(punchIns)) { log.info("用户:{} 没有查询到生效的打卡任务"); return List.of(); } // 获取一周的起始日期范围 List weeklyDateRange = DateUtils.getWeeklyDateRange(); // 获取打卡任务ID List punchInIds = punchIns.stream().map(PunchIn::getId).collect(Collectors.toList()); // 找出范围内的打卡记录 PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery(); punchInRecordQuery.setPunchInIds(punchInIds); punchInRecordQuery.setStartDate(weeklyDateRange.getFirst().toString()); punchInRecordQuery.setEndDate(weeklyDateRange.getLast().toString()); List punchInRecords = punchInRecordService.listByCondition(punchInRecordQuery); // 打卡任务-打卡记录 分组 Map> recordMap = punchInRecords.stream().collect(Collectors.groupingBy(PunchInRecord::getPunchInId)); // 日期格式化 SimpleDateFormat sdf = DateUtils.buildDateFormat(); // 当前日期 LocalDate today = LocalDate.now(); // 构建打卡记录 List punchInWithRecordVOS = new ArrayList<>(); // 初始化的时间记录 LocalTime initRecordTimeTrack = LocalTime.parse("00:00:00.000"); for (PunchIn punchIn : punchIns) { // 一周的打卡记录容器 List weeklyRecords = new ArrayList<>(); // 打卡任务信息 PunchInWithRecordVO punchInWithRecordVO = new PunchInWithRecordVO(); BeanUtils.copyProperties(punchIn, punchInWithRecordVO); punchInWithRecordVO.setPunchInId(punchIn.getId()); punchInWithRecordVO.setPunchInRecords(weeklyRecords); punchInWithRecordVO.setRecordTimeTrack(initRecordTimeTrack); punchInWithRecordVO.setRecordCountTrack(0); punchInWithRecordVOS.add(punchInWithRecordVO); // 打卡任务创建日期 LocalDate punchInCreationDate = LocalDate.parse(sdf.format(punchIn.getCreationTime())); // 获取打卡任务对应的打卡记录,并转为打卡日期-打卡记录map List records = Optional.ofNullable(recordMap.get(punchInWithRecordVO.getPunchInId())).orElse(List.of()); Map weeklyRecordMap = records.stream() .collect(Collectors.toMap(PunchInRecord::getPunchInDate, Function.identity(), (key1, key2) -> key1)); for (LocalDate weeklyDate : weeklyDateRange) { String weeklyDateStr = weeklyDate.toString(); // 获取打卡记录 PunchInRecord punchInRecord = weeklyRecordMap.get(weeklyDateStr); // 根据过往打卡记录设置打卡状态 PunchInStatusViewEnum punchInStatus = judgePunchInStatus(today, weeklyDate, punchInCreationDate, punchIn, punchInRecord); // 设置打卡记录 PunchInRecordVO punchInRecordVO = new PunchInRecordVO(); punchInRecordVO.setPunchInDate(weeklyDateStr); punchInRecordVO.setPunchInStatus(punchInStatus); weeklyRecords.add(punchInRecordVO); // 如果是今天的打卡记录,设置计数/计时属性 if (!Objects.isNull(punchInRecord) && today.isEqual(weeklyDate)) { punchInWithRecordVO.setRecordTimeTrack(punchInRecord.getTimeTrack()); punchInWithRecordVO.setRecordCountTrack(punchInRecord.getCountTrack()); } // 如果是今天设置状态控制页面显示 if (today.isEqual(weeklyDate)) { punchInWithRecordVO.setPunchInStatus(punchInStatus); } } } return punchInWithRecordVOS; } private PunchInStatusViewEnum judgePunchInStatus(LocalDate today, LocalDate weeklyDate, LocalDate punchInCreationDate, PunchIn punchIn, PunchInRecord punchInRecord) { // 一周某天还没到,无法打卡 if (weeklyDate.isAfter(today)) { return PunchInStatusViewEnum.FUTURE_TIME; } // 一周某天早于任务创建时间,无法打卡 if (weeklyDate.isBefore(punchInCreationDate)) { return PunchInStatusViewEnum.UNCREATED; } // 一周某天在今天之前存在打卡记录,则需要判断是否完成打卡 if (!Objects.isNull(punchInRecord) && weeklyDate.isBefore(today)) { if (PunchInStatusV1Enum.FINISH.equals(punchInRecord.getPunchInStatus()) || PunchInStatusV1Enum.REMAKE_FINISH.equals(punchInRecord.getPunchInStatus())) { return PunchInStatusViewEnum.PUNCH_IN; } } // 一周的某天是今天,且存在打卡记录,则判断是否完成打卡 if (weeklyDate.isEqual(today) && !Objects.isNull(punchInRecord)) { PunchInStatusV1Enum punchInStatusV1Enum = settleManager.judgePunchInStatus(punchIn, punchInRecord); if (PunchInStatusV1Enum.FINISH.equals(punchInStatusV1Enum) || PunchInStatusV1Enum.REMAKE_FINISH.equals(punchInStatusV1Enum)) { return PunchInStatusViewEnum.PUNCH_IN; } // 当天存在打卡记录,但是还没满足打卡条件,则认为打卡状态未知 return PunchInStatusViewEnum.TODAY_UNKNOWN; } // 一周的某天是今天,且不存在打卡记录,则还没进行打卡 if (weeklyDate.isEqual(today) && Objects.isNull(punchInRecord)) { return PunchInStatusViewEnum.TODAY_UNKNOWN; } // 不符合任何情况则是未打卡 return PunchInStatusViewEnum.UN_PUNCH_IN; } @Override public PunchInVO queryPunchInById(Long punchInId) { Assert.isNullInBusiness(punchInId, "请传入待查询的任务ID"); return Optional.ofNullable(punchInService.getById(punchInId)).map(punchIn -> { PunchInVO punchInVO = new PunchInVO(); BeanUtils.copyProperties(punchIn, punchInVO); return punchInVO; }).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务")); } @Override public void saveOrUpdatePunchIn(PunchInRequest request) { Assert.isNullInBusiness(request, "请传入任务信息"); if (PunchInCategoryEnum.COUNT.equals(request.getCategory()) && (Objects.isNull(request.getRule()) || Objects.isNull(request.getCountTrack()))) { throw BusinessException.fail("打卡类型:计数,比较规则和次数不能为空"); } if (PunchInCategoryEnum.TIME.equals(request.getCategory()) && (Objects.isNull(request.getRule()) || Objects.isNull(request.getTimeTrack()))) { throw BusinessException.fail("打卡类型:计时,比较规则和时间不能为空"); } PunchIn punchIn = new PunchIn(); BeanUtils.copyProperties(request, punchIn); punchIn.setArchiveFlag(false); if (Objects.isNull(punchIn.getId())) { punchInService.insert(punchIn); } else { punchInService.update(punchIn); } } @Override public void deletePunchIn(Long punchInId) { Assert.isNullInBusiness(punchInId, "请传入待删除的任务"); punchInService.delete(punchInId); } @Override @Transactional(rollbackFor = Exception.class) public void doPunchIn(PunchInRequest request) { if (Objects.isNull(request) || Objects.isNull(request.getId())) { BusinessException.throwFail("请传入待打卡的任务"); } PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getId())) .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务")); if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory()) && Objects.isNull(request.getTimeTrack())) { BusinessException.throwFail("打卡类型:计时,请传入时间记录"); } // 查询今天的打卡记录 LocalDate today = LocalDate.now(); PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery(); punchInRecordQuery.setPunchInDate(today.toString()); punchInRecordQuery.setPunchInId(request.getId()); PunchInRecord oldPunchInRecord = punchInRecordService.selectOneByCondition(punchInRecordQuery); // 打卡类型:单次打卡,需要判断是否重复打卡 if (PunchInCategoryEnum.SINGLE.equals(punchIn.getCategory()) && !Objects.isNull(oldPunchInRecord)) { throw BusinessException.fail("已打卡,无须重复打卡"); } // 获取或创建打卡记录 PunchInRecord punchInRecord = Optional.ofNullable(oldPunchInRecord).orElseGet(() -> { PunchInRecord record = new PunchInRecord(); record.setPunchInId(request.getId()); record.setPunchInDate(LocalDate.now().toString()); return record; }); // 打卡类型:计数,需要累加打卡次数 if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())) { punchInRecord.setCountTrack(Optional.ofNullable(punchInRecord.getCountTrack()).orElse(0) + 1); } // 打卡类型:计时,需要记录最新打卡时长 if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) { punchInRecord.setTimeTrack(request.getTimeTrack()); } // 新增或更新 if (Objects.isNull(punchInRecord.getId())) { punchInRecordService.insert(punchInRecord); } else { punchInRecordService.update(punchInRecord); } } @Override public void archivePunchIn(Long punchInId) { Assert.isNullInBusiness(punchInId, "请选择待归档的任务"); PunchIn punchIn = new PunchIn(); punchIn.setId(punchInId); punchIn.setArchiveFlag(true); punchInService.update(punchIn); } @Override public void remakePunchIn(PunchInRecordRequest request) { // 待补卡的打卡日期 LocalDate punchInDate = LocalDate.parse(request.getPunchInDate()); // 如果补打卡日期是今天,则不允许补卡 if (punchInDate.isEqual(LocalDate.now())) { BusinessException.throwFail("补打卡日期不能为今天"); } // 获取打卡任务 PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getPunchInId())) .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务")); // 补打卡的打卡日期不能早于打卡任务创建日期 if (punchInDate.isBefore(punchIn.getCreationTime().toLocalDateTime().toLocalDate())) { BusinessException.throwFail("补打卡日期不能早于打卡任务创建日期"); } settleManager.settleHandler(PunchInSettleTypeEnum.REMAKE, punchInDate, Arrays.asList(UserUtils.getCurrentUserId()), Arrays.asList(request.getPunchInId())); } @Override public void revokePunchIn(PunchInRecordRequest request) { // 获取打卡任务 PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getPunchInId())) .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务")); PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery(); punchInRecordQuery.setPunchInId(request.getPunchInId()); punchInRecordQuery.setPunchInDate(LocalDate.now().toString()); PunchInRecord punchInRecord = punchInRecordService.selectOneByCondition(punchInRecordQuery); if (Objects.isNull(punchInRecord)) { log.info("打卡任务:{},打卡日期:{} 没有找到当天打卡记录,进行撤销", punchInRecordQuery.getPunchInId(), punchInRecordQuery.getPunchInDate()); return; } // 单次打卡/计时打卡 直接删除记录即可 if (PunchInCategoryEnum.SINGLE.equals(punchIn.getCategory()) || PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) { punchInRecordService.delete(punchInRecord.getId()); } // 计数打卡,需要减去打卡次数 if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory()) && Optional.ofNullable(punchInRecord.getCountTrack()).orElse(0) > 0) { PunchInRecord updateRecord = new PunchInRecord(); updateRecord.setId(punchInRecord.getId()); updateRecord.setCountTrack(punchInRecord.getCountTrack() - 1); punchInRecordService.update(updateRecord); if (updateRecord.getCountTrack() == 0) { punchInRecordService.delete(punchInRecord.getId()); } } } @Override public PunchInDataVO queryPunchInData(PunchInDataQuery query) { PunchIn punchIn = Optional.ofNullable(punchInService.getById(query.getId())) .orElseThrow(() -> BusinessException.fail("找到指定的打卡任务")); SimpleDateFormat sdf = DateUtils.buildDateFormat(); YearMonth yearMonth = YearMonth.of(query.getYear(), query.getMonth()); // 本月第一日 LocalDate firstOfMonth = yearMonth.atDay(1); // 本月最后以日 LocalDate endOfMonth = yearMonth.atEndOfMonth(); // 任务创建日期 LocalDate punchInCreationDate = punchIn.getCreationTime().toLocalDateTime().toLocalDate(); // 获取打卡记录 PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery(); punchInRecordQuery.setPunchInId(punchIn.getId()); punchInRecordQuery.setStartDate(String.format("%s 00:00:00.000", firstOfMonth)); punchInRecordQuery.setEndDate(String.format("%s 23:59:59.999", endOfMonth)); List punchInRecords = punchInRecordService.listByCondition(punchInRecordQuery); // 构造数据,日历部分的数据只需要已打卡的数据 List punchInCalendarDataVOS = new ArrayList<>(); List punchInRecordDtoList = new ArrayList<>(); for (PunchInRecord punchInRecord : punchInRecords) { PunchInRecordDataVO punchInRecordDataVO = new PunchInRecordDataVO(); BeanUtils.copyProperties(punchInRecord, punchInRecordDataVO); punchInRecordDtoList.add(punchInRecordDataVO); if (PunchInStatusV1Enum.UN_FINISH.equals(punchInRecord.getPunchInStatus()) || PunchInStatusV1Enum.DOING.equals(punchInRecord.getPunchInStatus())) { continue; } PunchInCalendarDataVO punchInCalendarDataVO = new PunchInCalendarDataVO(); punchInCalendarDataVO.setPunchInDate(punchInRecord.getPunchInDate()); punchInCalendarDataVO.setInfo("打卡"); punchInCalendarDataVO.setDate(punchInRecord.getPunchInDate()); punchInCalendarDataVOS.add(punchInCalendarDataVO); } // 计算全勤率 BigDecimal punchInRate = BigDecimal.ZERO; // 要考虑任务刚创建的情况,任务较迟创建的话则使用任务创建时间 long dayLength = ChronoUnit.DAYS .between(firstOfMonth.isBefore(punchInCreationDate) ? punchInCreationDate : firstOfMonth, LocalDate.now()); if (dayLength != 0) { punchInRate = BigDecimal.valueOf(punchInCalendarDataVOS.size()) .divide(BigDecimal.valueOf(dayLength), 4, RoundingMode.HALF_DOWN).multiply(ONE_HUNDRED); } // 构造返回结果 PunchInDataVO punchInDataVO = new PunchInDataVO(); punchInDataVO.setStartDate(sdf.format(punchIn.getCreationTime())); punchInDataVO.setEndDate(LocalDate.now().toString()); punchInDataVO.setPunchInNum(punchInCalendarDataVOS.size()); punchInDataVO.setCalendarSelected(punchInCalendarDataVOS); punchInDataVO.setPunchInRecords(punchInRecordDtoList); punchInDataVO.setPunchInRate(punchInRate); return punchInDataVO; } /** * 默认打卡完成率 */ private static final BigDecimal DEFAULT_PUNCH_IN_DONE_RATE = new BigDecimal("101.00"); @Autowired private ICalendarManager calendarManager; @Override public boolean judgeRepeat(PunchInTask punchInTask, String repeatDateStr) { // 每天 if (RepeatCategoryEnum.EVERYDAY.equals(punchInTask.getRepeatCategory())) { return true; } // 法定工作日 if (RepeatCategoryEnum.WORKDAY.equals(punchInTask.getRepeatCategory())) { return !calendarManager.judgeHoliday(repeatDateStr); } // 法定节假日(含周末) if (RepeatCategoryEnum.HOLIDAY.equals(punchInTask.getRepeatCategory())) { return !calendarManager.judgeHoliday(repeatDateStr); } // 自定义 if (RepeatCategoryEnum.CUSTOM.equals(punchInTask.getRepeatCategory())) { if (!StringUtils.hasText(punchInTask.getRepeatCustomDay())) { BusinessException.throwFail(String.format("打卡任务ID:%s, 重复周期类型:自定义重复,没有设定自定义重复日期")); } LocalDate repeatDate = LocalDate.parse(repeatDateStr); int dayOfWeekValue = repeatDate.getDayOfWeek().getValue(); return punchInTask.getRepeatCustomDay().contains(String.valueOf(dayOfWeekValue)); } return false; } @Override public PunchInStatusEnum judgePunchInStatusInTask(PunchInTask punchInTask, PunchInTaskHistory punchInTaskHistory, boolean holidayFlag) { Assert.isNullInBusiness(punchInTask, "打卡任务不能为空 "); // 没有打卡记录,直接没完成 if (Objects.isNull(punchInTaskHistory)) { return PunchInStatusEnum.UNDONE; } // 单次打卡,不区分是否节假日 if (PunchInMethodEnum.SINGLE.equals(punchInTask.getPunchInMethod())) { int countTrack = Optional.ofNullable(punchInTask.getCountTrack()).orElse(0); return countTrack > 0 ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE; } // 打卡任务的节假日启用配置 CommonEnableStatusEnum holidayStatus = Optional.ofNullable(punchInTask.getHolidayStatus()).orElse(CommonEnableStatusEnum.DISABLED); boolean enableHolidayFlag = CommonEnableStatusEnum.ENABLED.equals(holidayStatus) && holidayFlag; // 计数打卡,要区分是否节假日使用不同的判断标准 if (PunchInMethodEnum.COUNT.equals(punchInTask.getPunchInMethod())) { int countTrack = enableHolidayFlag ? punchInTask.getHolidayCountTrack() : punchInTask.getCountTrack(); int punchInCountTrack = Optional.ofNullable(punchInTaskHistory.getCountTrack()).orElse(0); if (CompareRuleEnum.GTE.equals(punchInTask.getCompareRule()) && punchInCountTrack >= countTrack) { return PunchInStatusEnum.DONE; } if (CompareRuleEnum.LTE.equals(punchInTask.getCompareRule()) && punchInCountTrack <= countTrack) { return PunchInStatusEnum.DONE; } return PunchInStatusEnum.UNDONE; } // 计时打卡,要区分是否节假日使用不同的判断标准 if (PunchInCategoryEnum.TIME.equals(punchInTask.getPunchInMethod())) { LocalTime timeTrack = enableHolidayFlag ? punchInTask.getHolidayTimeTrack() : punchInTask.getTimeTrack(); LocalTime punchInTimeTrack = Optional.ofNullable(punchInTaskHistory.getTimeTrack()).orElse(LocalTime.parse("00:00:00.000")); if (CompareRuleEnum.GTE.equals(punchInTask.getCompareRule()) && punchInTimeTrack.compareTo(timeTrack) > -1) { return PunchInStatusEnum.DONE; } if (CompareRuleEnum.LTE.equals(punchInTask.getCompareRule()) && punchInTimeTrack.compareTo(timeTrack) < 1) { return PunchInStatusEnum.DONE; } return PunchInStatusEnum.UNDONE; } return PunchInStatusEnum.UNDONE; } @Override public PunchInStatusEnum judgePunchInStatusInMultiTask(PunchInMultiTask punchInMultiTask, List punchInTaskHistoryList, int multiTaskNum) { if (CollectionUtils.isEmpty(punchInTaskHistoryList) || multiTaskNum == 0) { return PunchInStatusEnum.UNDONE; } // 完成的打卡数量 long punchInDoneCount = punchInTaskHistoryList.stream().filter(v -> PunchInStatusEnum.DONE.equals(v.getPunchInStatus())).count(); // 次数,当天打卡次数大于等于设置的次数,则完成打卡 if (PunchInMethodMultiEnum.COUNT.equals(punchInMultiTask.getPunchInMethod())) { // 没有设置时默认设置一个较大的数,避免出现打卡次数为0的情况 Integer doneCount = Optional.ofNullable(punchInMultiTask.getPunchInDoneCount()).orElse(punchInTaskHistoryList.size() + 1); return punchInDoneCount >= doneCount ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE; } // 比率,当天打卡完成比率大于等于设置的比率,则完成打卡 if (PunchInMethodMultiEnum.RATE.equals(punchInMultiTask.getPunchInMethod())) { // 没有设置时默认设置一个较大的数,避免出现打卡完成率为0的情况 BigDecimal doneRate = Optional.ofNullable(punchInMultiTask.getPunchInDoneRate()).orElse(DEFAULT_PUNCH_IN_DONE_RATE); BigDecimal punchInDoneRate = BigDecimal.valueOf(punchInDoneCount).divide(BigDecimal.valueOf(multiTaskNum), 2, RoundingMode.HALF_UP); return punchInDoneRate.compareTo(doneRate) > -1 ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE; } return PunchInStatusEnum.UNDONE; } @Override public int calculatePointsInTask(PunchInTask punchInTask, List punchInTaskExts, PunchInTaskHistory punchInTaskHistory, PunchInStatsWeek punchInStatsWeek, PunchInStatsMonth punchInStatsMonth, PunchInStatus punchInStatus) { // 未完成打卡,积分为0 if (PunchInStatusEnum.UNDONE.equals(punchInTaskHistory.getPunchInStatus())) { return 0; } // 单次打卡中使用的拓展信息 List punchInExtList = punchInTaskExts.stream().filter(v -> PunchInDimensionEnum.ONE_DAY.equals(v.getDimension())).collect(Collectors.toList()); // 打卡任务使用的拓展信息 List punchInTaskExtList = punchInTaskExts.stream().filter(v -> PunchInDimensionEnum.MULTI_DAY.equals(v.getDimension())).collect(Collectors.toList()); // 单个任务积分=基本积分+(可选)额外积分+(可选)连续完成额外积分+法定节假日(含周末)双倍奖励+全勤双倍奖励 // 基本积分 int basicPoints = Optional.ofNullable(punchInTask.getPoints()).orElse(0); // 额外积分 basicPoints += calculateExtraPointsInTask(punchInTask, punchInExtList, punchInTaskHistory, calendarManager.judgeHoliday(punchInTaskHistory.getPunchInDate())); // 启用了全勤奖励 int fullAttendancePoints = 0; if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getFullAttendanceStatus())) { LocalDate punchInDate = LocalDate.parse(punchInTaskHistory.getPunchInDate()); // 结算周期:周,并且结算日是周末,则双倍奖励; if (FullAttendancePeriodEnum.WEEK.equals(punchInTask.getFullAttendancePeriod()) && punchInDate.getDayOfWeek().getValue() == 7) { int undoneCount = punchInStatsWeek.getPunchInTotalCount() - punchInStatsWeek.getPunchInDoneCount(); if (undoneCount <= punchInTask.getFullAttendanceFaultToleranceCnt()) { fullAttendancePoints = basicPoints; } } // 结算周期:月,结算日是当月最后一天,则双倍奖励; if (FullAttendancePeriodEnum.MONTH.equals(punchInTask.getFullAttendancePeriod()) && punchInDate.lengthOfMonth() == punchInDate.getDayOfMonth()) { int undoneCount = punchInStatsMonth.getPunchInTotalCount() - punchInStatsMonth.getPunchInDoneCount(); if (undoneCount <= punchInTask.getFullAttendanceFaultToleranceCnt()) { fullAttendancePoints = basicPoints; } } } // 启用了法定节假日(含周末)双倍奖励 int holidayPoints = 0; if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getHolidayStatus()) && calendarManager.judgeHoliday(punchInTaskHistory.getPunchInDate())) { holidayPoints = basicPoints; } // 连续完成额外积分 int taskPoints = 0; if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getTaskPointsStatus()) && ConsecutiveStatusEnum.CONSECUTIVE.equals(punchInStatus.getConsecutiveStatus())) { // punchInTaskExtList 根据InitialValue倒序排列 punchInTaskExtList.sort(Comparator.comparing(PunchInTaskExt::getInitialValue).reversed()); // 第二轮标志 boolean secondRound = false; // 上一轮的值 int prevInitialValue = 0; for (PunchInTaskExt punchInTaskExt : punchInTaskExtList) { // 比较结果 int compareValue = punchInStatus.getConsecutiveDay().compareTo(punchInTaskExt.getInitialValue()); // 如果连续日期小于initialValue,则跳过 if (compareValue == -1) { continue; } // 额外奖励数 int extraCount = 0; // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算 if (secondRound) { extraCount = prevInitialValue - punchInTaskExt.getInitialValue(); } else if (compareValue >= 0) { extraCount = punchInStatus.getConsecutiveDay() - punchInTaskExt.getInitialValue() + 1; secondRound = true; } taskPoints += punchInTaskExt.getExtraPoints() * extraCount; prevInitialValue = punchInTaskExt.getInitialValue(); } } return basicPoints + fullAttendancePoints + holidayPoints + taskPoints; } /** * 计算额外积分 * @param punchInTask * @param punchInTaskExtList * @return */ private int calculateExtraPointsInTask(PunchInTask punchInTask, List punchInTaskExtList, PunchInTaskHistory punchInTaskHistory, boolean holidayFlag) { // 单次打卡或者没有启用额外积分计算则跳过 if (PunchInExtraMethodEnum.NONE.equals(punchInTask.getExtraMethod()) || PunchInMethodEnum.SINGLE.equals(punchInTask.getPunchInMethod())) { return 0; } // 计算超出部分, int extraCountTrack = 0; // 计数打卡 if (PunchInMethodEnum.COUNT.equals(punchInTask.getPunchInMethod())) { extraCountTrack = punchInTaskHistory.getCountTrack() - (holidayFlag ? punchInTask.getHolidayCountTrack() : punchInTask.getCountTrack()); } // 计时打卡 if (PunchInMethodEnum.TIMING.equals(punchInTask.getPunchInMethod())) { // 超出的时间按照时间间隔转换成次数 long timeTrack = punchInTaskHistory.getTimeTrack().until((holidayFlag ? punchInTask.getHolidayTimeTrack() : punchInTask.getTimeTrack()), ChronoUnit.MINUTES); extraCountTrack = BigDecimal.valueOf(timeTrack).divide(BigDecimal.valueOf(punchInTask.getExtraTimeStep()), 0, RoundingMode.FLOOR).intValue(); } // 如果等于0则没有超出部分,不用计算 if (extraCountTrack == 0) { return 0; } // 固定计算 if (PunchInExtraMethodEnum.FIXED.equals(punchInTask.getExtraMethod())) { Integer extraPoints = Optional.ofNullable(punchInTask.getExtraPoints()).orElse(0); return extraCountTrack * extraPoints; } // 区间计算 if (PunchInExtraMethodEnum.INTERVAL.equals(punchInTask.getExtraMethod()) && !CollectionUtils.isEmpty(punchInTaskExtList)) { int basicPoints = 0; int prevInitialValue = 0; for (PunchInTaskExt punchInTaskExt : punchInTaskExtList) { Integer initialValue = Optional.ofNullable(punchInTaskExt.getInitialValue()).orElse(0); // 第一轮:initialValue - extraCountTrack的值v1 // 如果v1≤0则超出initialValue范围,使用initialValue计算; // 如果v1>0则在initialValue范围内 使用extraCountTrack // 后续轮:本轮initialValue - extraCountTrack的值v1、 // 如果v1≤0则超出本轮initialValue范围,使用上一轮initialValue-本轮initialValue的值v2计算; // 如果v1>0则在本轮initialValue范围内 使用extraCountTrack-上一轮initialValue的值v3计算 // 下面的代码不适合第一轮的计算 if (initialValue - extraCountTrack <= 0) { basicPoints = punchInTaskExt.getExtraPoints() * Math.abs(prevInitialValue - initialValue); } else { basicPoints = punchInTaskExt.getExtraPoints() * (extraCountTrack - prevInitialValue); } prevInitialValue = initialValue; } return basicPoints; } return 0; } @Override public int calculatePointsInMultiTask(PunchInMultiTask punchInMultiTask, List punchInMultiTaskExts, PunchInMultiTaskHistory punchInMultiTaskHistory, PunchInStatus punchInStatus) { // 未完成打卡或没有启用多任务积分计算,积分为0 if (PunchInStatusEnum.UNDONE.equals(punchInMultiTaskHistory.getPunchInStatus()) || CommonEnableStatusEnum.ENABLED.equals(punchInMultiTask.getTaskPointsStatus())) { return 0; } // 单次打卡中使用的拓展信息 List punchInExtList = punchInMultiTaskExts.stream().filter(v -> PunchInDimensionEnum.ONE_DAY.equals(v.getDimension())).collect(Collectors.toList()); // 打卡任务使用的拓展信息 List punchInTaskExtList = punchInMultiTaskExts.stream().filter(v -> PunchInDimensionEnum.MULTI_DAY.equals(v.getDimension())).collect(Collectors.toList()); // 多个任务积分=多任务基本积分+多任务额外积分+连续完成额外积分 // 基本积分 int basicPoints = punchInMultiTask.getPoints(); // 额外积分 int extraPoints = 0; // 额外次数 int extractCount = punchInMultiTaskHistory.getPunchInDoneCount() - punchInMultiTask.getPunchInDoneCount(); // 固定计算 if (PunchInExtraMethodEnum.FIXED.equals(punchInMultiTask.getExtraMethod())) { extraPoints = extractCount * punchInMultiTask.getExtraPoints(); } // 区间计算 if (PunchInExtraMethodEnum.INTERVAL.equals(punchInMultiTask.getExtraMethod()) && !CollectionUtils.isEmpty(punchInMultiTaskExts)) { // punchInTaskExtList 根据InitialValue倒序排列 punchInExtList.sort(Comparator.comparing(PunchInMultiTaskExt::getInitialValue).reversed()); // 第二轮标志 boolean secondRound = false; // 上一轮的值 int prevInitialValue = 0; for (PunchInMultiTaskExt punchInTaskExt : punchInTaskExtList) { // 比较结果 int compareValue = Integer.compare(extractCount, punchInTaskExt.getInitialValue()); // 如果连续日期小于initialValue,则跳过 if (compareValue == -1) { continue; } // 额外奖励数 int tempExtraCount = 0; // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算 if (secondRound) { tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue(); } else if (compareValue >= 0) { tempExtraCount = extractCount - punchInTaskExt.getInitialValue() + 1; secondRound = true; } extraPoints += punchInTaskExt.getExtraPoints() * tempExtraCount; prevInitialValue = punchInTaskExt.getInitialValue(); } } // 连续完成额外积分 int taskPoints = 0; if (CommonEnableStatusEnum.ENABLED.equals(punchInMultiTask.getTaskPointsStatus()) && ConsecutiveStatusEnum.CONSECUTIVE.equals(punchInStatus.getConsecutiveStatus())) { // punchInTaskExtList 根据InitialValue倒序排列 punchInTaskExtList.sort(Comparator.comparing(PunchInMultiTaskExt::getInitialValue).reversed()); // 第二轮标志 boolean secondRound = false; // 上一轮的值 int prevInitialValue = 0; for (PunchInMultiTaskExt punchInTaskExt : punchInTaskExtList) { // 比较结果 int compareValue = punchInStatus.getConsecutiveDay().compareTo(punchInTaskExt.getInitialValue()); // 如果连续日期小于initialValue,则跳过 if (compareValue == -1) { continue; } // 额外奖励数 int tempExtraCount = 0; // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算 if (secondRound) { tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue(); } else if (compareValue >= 0) { tempExtraCount = punchInStatus.getConsecutiveDay() - punchInTaskExt.getInitialValue() + 1; secondRound = true; } taskPoints += punchInTaskExt.getExtraPoints() * tempExtraCount; prevInitialValue = punchInTaskExt.getInitialValue(); } } return basicPoints + extraPoints + taskPoints; } }