PunchInManagerImpl.java 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690
  1. package com.punchsettle.server.service.manager.impl;
  2. import java.time.LocalDate;
  3. import java.time.LocalTime;
  4. import java.time.format.DateTimeFormatter;
  5. import java.util.Arrays;
  6. import java.util.Comparator;
  7. import java.util.List;
  8. import java.util.Map;
  9. import java.util.Objects;
  10. import java.util.Optional;
  11. import java.util.Set;
  12. import java.util.function.Function;
  13. import java.util.stream.Collectors;
  14. import org.springframework.beans.BeanUtils;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.cache.annotation.Cacheable;
  17. import org.springframework.stereotype.Service;
  18. import org.springframework.transaction.annotation.Transactional;
  19. import org.springframework.util.CollectionUtils;
  20. import org.springframework.util.StringUtils;
  21. import com.punchsettle.server.atomic.StatPiTask;
  22. import com.punchsettle.server.atomic.entity.PiStatus;
  23. import com.punchsettle.server.atomic.entity.PiTask;
  24. import com.punchsettle.server.atomic.entity.PiTaskHistory;
  25. import com.punchsettle.server.atomic.service.IPiStatusService;
  26. import com.punchsettle.server.atomic.service.IPiTaskHistoryService;
  27. import com.punchsettle.server.atomic.service.IPiTaskService;
  28. import com.punchsettle.server.atomic.service.IStatPiTaskMonthService;
  29. import com.punchsettle.server.atomic.service.IStatPiTaskYearService;
  30. import com.punchsettle.server.common.constant.CommonEnableStatusEnum;
  31. import com.punchsettle.server.common.exception.BusinessException;
  32. import com.punchsettle.server.common.utils.Assert;
  33. import com.punchsettle.server.constant.ArchiveStatusEnum;
  34. import com.punchsettle.server.constant.CacheNameConstant;
  35. import com.punchsettle.server.constant.ContinueStatusEnum;
  36. import com.punchsettle.server.constant.PunchInExtraMethodEnum;
  37. import com.punchsettle.server.constant.PunchInMethodEnum;
  38. import com.punchsettle.server.constant.PunchInResultEnum;
  39. import com.punchsettle.server.constant.PunchInResultViewEnum;
  40. import com.punchsettle.server.constant.RepeatCategoryEnum;
  41. import com.punchsettle.server.constant.StatPeriodEnum;
  42. import com.punchsettle.server.constant.VersionStatusEnum;
  43. import com.punchsettle.server.pojo.punchIn.PiTaskHistoryQuery;
  44. import com.punchsettle.server.pojo.punchIn.PiTaskHistorySimpleVO;
  45. import com.punchsettle.server.pojo.punchIn.PiTaskHistoryStatVO;
  46. import com.punchsettle.server.pojo.punchIn.PiTaskQuery;
  47. import com.punchsettle.server.pojo.punchIn.PiTaskRequest;
  48. import com.punchsettle.server.pojo.punchIn.PiTaskSimpleVO;
  49. import com.punchsettle.server.pojo.punchIn.PiTaskStatQuery;
  50. import com.punchsettle.server.pojo.punchIn.PiTaskStatVO;
  51. import com.punchsettle.server.pojo.punchIn.PiTaskToDoVO;
  52. import com.punchsettle.server.pojo.punchIn.PiTaskVO;
  53. import com.punchsettle.server.pojo.punchIn.PunchInRequest;
  54. import com.punchsettle.server.pojo.stat.StatPiTaskQuery;
  55. import com.punchsettle.server.pojo.ucharts.LineSeriesVO;
  56. import com.punchsettle.server.pojo.ucharts.LineVO;
  57. import com.punchsettle.server.service.manager.ICacheManager;
  58. import com.punchsettle.server.service.manager.ICalendarManager;
  59. import com.punchsettle.server.service.manager.IPunchInCoreManager;
  60. import com.punchsettle.server.service.manager.IPunchInManager;
  61. import com.punchsettle.server.utiis.DateUtils;
  62. import com.punchsettle.server.utiis.SpringUtils;
  63. import com.punchsettle.server.utiis.UserUtils;
  64. import lombok.extern.slf4j.Slf4j;
  65. /**
  66. * @author tyuio
  67. * @version 1.0.0
  68. * @description 打卡结算任务 服务类
  69. * @date 2024/11/25 15:12
  70. */
  71. @Slf4j
  72. @Service
  73. public class PunchInManagerImpl implements IPunchInManager {
  74. @Autowired
  75. private IPiTaskService piTaskService;
  76. @Autowired
  77. private IPiTaskHistoryService piTaskHistoryService;
  78. @Autowired
  79. private IPunchInCoreManager punchInCoreManager;
  80. @Autowired
  81. private IStatPiTaskYearService statPiTaskYearService;
  82. @Autowired
  83. private IStatPiTaskMonthService statPiTaskMonthService;
  84. @Autowired
  85. private ICalendarManager calendarManager;
  86. @Autowired
  87. private ICacheManager cacheManager;
  88. @Autowired
  89. private IPiStatusService piStatusService;
  90. /**
  91. * 默认时间00:00:00.000
  92. */
  93. private static final LocalTime ZERO_TIME = LocalTime.parse("00:00:00.000");
  94. @Override
  95. public List<PiTaskToDoVO> queryToDoList(Long currentUserId) {
  96. Assert.isNull(currentUserId);
  97. // 查询打卡任务
  98. List<PiTask> piTasks = SpringUtils.getBean(IPunchInManager.class).getTaskListInCache(currentUserId);
  99. if (CollectionUtils.isEmpty(piTasks)) {
  100. return List.of();
  101. }
  102. // 当前日期
  103. LocalDate today = LocalDate.now();
  104. String todayStr = DateUtils.YYYY_MM_DD_FORMATTER.format(today);
  105. // 当前时间
  106. LocalTime nowTime = LocalTime.now();
  107. // 当前是否节假日
  108. boolean holidayFlag = calendarManager.judgeHoliday(todayStr);
  109. // 查询打卡记录
  110. List<PiTaskHistory> piTaskHistories = SpringUtils.getBean(IPunchInManager.class).getTaskHistoryListInCache(currentUserId, todayStr);
  111. // 打卡任务唯一ID-打卡记录关联
  112. Map<Long, PiTaskHistory> piTaskHistoryMap = piTaskHistories.stream().collect(Collectors.toMap(PiTaskHistory::getTaskUniqueId, Function.identity(), (key1, key2) -> key1));
  113. // 1.判断重复频率,只显示今天待打卡的任务
  114. // 2.判断显示时间,还没到点显示的暂时不显示
  115. // 3.根据displayOrder进行排序,按自然顺序排列
  116. return piTasks.stream().filter(piTask -> punchInCoreManager.judgeRepeat(piTask, today))
  117. .filter(piTask -> {
  118. LocalTime displayTime = Optional.ofNullable(piTask.getDisplayTime()).orElse(ZERO_TIME);
  119. return nowTime.compareTo(displayTime) > -1;
  120. })
  121. .sorted(Comparator.comparing(piTask -> Optional.ofNullable(piTask.getDisplayOrder()).orElse(0)))
  122. .map(piTask -> {
  123. PiTaskToDoVO piTaskToDoVO = new PiTaskToDoVO();
  124. BeanUtils.copyProperties(piTask, piTaskToDoVO);
  125. piTaskToDoVO.setPunchInResult(PunchInResultEnum.UNDONE);
  126. piTaskToDoVO.setHolidayFlag(CommonEnableStatusEnum.ENABLED.equals(piTask.getHolidayStatus()) && holidayFlag);
  127. // 获取并设置已打卡记录
  128. PiTaskHistory piTaskHistory = piTaskHistoryMap.get(piTask.getUniqueId());
  129. if (Objects.nonNull(piTaskHistory)) {
  130. piTaskToDoVO.setPunchInResult(piTaskHistory.getPunchInResult());
  131. piTaskToDoVO.setCurrentCountTrack(piTaskHistory.getCountTrack());
  132. piTaskToDoVO.setCurrentTimeTrack(piTaskHistory.getTimeTrack());
  133. }
  134. return piTaskToDoVO;
  135. }).collect(Collectors.toList());
  136. }
  137. @Override
  138. @Cacheable(cacheNames = CacheNameConstant.TASK_LIST_VO, key = "T(com.punchsettle.server.utiis.UserUtils).getCurrentUserId()")
  139. public List<PiTaskSimpleVO> queryTaskList() {
  140. Long currentUserId = UserUtils.getCurrentUserId();
  141. // 查询打卡任务
  142. List<PiTask> piTasks = piTaskService.getActiveTask(Arrays.asList(currentUserId));
  143. if (CollectionUtils.isEmpty(piTasks)) {
  144. return List.of();
  145. }
  146. // 当前日期
  147. LocalDate today = LocalDate.now();
  148. LocalDate beforeToady = today.minusDays(5);
  149. LocalDate afterToady = today.plusDays(4);
  150. // 获取日期范围
  151. List<LocalDate> weeklyDateRange = DateUtils.getDateRange(beforeToady, afterToady);
  152. // 打卡任务唯一ID
  153. Set<Long> taskUniqueIds = piTasks.stream().map(PiTask::getUniqueId).collect(Collectors.toSet());
  154. // 找出范围内的打卡记录
  155. PiTaskHistoryQuery piTaskHistoryQuery = new PiTaskHistoryQuery();
  156. piTaskHistoryQuery.setTaskUniqueIds(taskUniqueIds);
  157. piTaskHistoryQuery.setUserIds(Arrays.asList(currentUserId));
  158. piTaskHistoryQuery.setPunchInDateFrom(weeklyDateRange.getFirst().toString());
  159. piTaskHistoryQuery.setPunchInDateTo(weeklyDateRange.getLast().toString());
  160. List<PiTaskHistory> piTaskHistories = piTaskHistoryService.queryByCondition(piTaskHistoryQuery);
  161. // 打卡任务唯一ID-打卡记录关联
  162. Map<Long, List<PiTaskHistory>> piTaskHistoryMap = piTaskHistories.stream().collect(Collectors.groupingBy(PiTaskHistory::getTaskUniqueId));
  163. // 按顺序排列
  164. return piTasks.stream()
  165. .sorted(Comparator.comparing(piTask -> Optional.ofNullable(piTask.getDisplayOrder()).orElse(0)))
  166. .map(piTask -> {
  167. PiTaskSimpleVO piTaskSimpleVO = new PiTaskSimpleVO();
  168. BeanUtils.copyProperties(piTask, piTaskSimpleVO);
  169. // 获取对应的打卡记录
  170. List<PiTaskHistory> tempPiTaskHistories = Optional.ofNullable(piTaskHistoryMap.get(piTask.getUniqueId())).orElse(List.of());
  171. // 任务创建日期
  172. LocalDate creationDate = piTask.getCreationTime().toLocalDateTime().toLocalDate();
  173. // 打卡日期-打卡记录关联
  174. Map<String, PiTaskHistory> tempPiTaskHistoryMap = tempPiTaskHistories.stream().collect(Collectors.toMap(PiTaskHistory::getPunchInDate, Function.identity(), (key1, key2) -> key1));
  175. // 按时间范围遍历,构造页面打卡结果
  176. List<PiTaskHistorySimpleVO> piTaskHistorySimpleVOS = weeklyDateRange.stream().map(punchInDate -> {
  177. // 打卡日期
  178. String punchInDateStr = punchInDate.toString();
  179. // 判断页面打卡结果
  180. PiTaskHistory piTaskHistory = tempPiTaskHistoryMap.get(punchInDateStr);
  181. PunchInResultViewEnum punchInResultView = judgePunchInResultView(piTask, piTaskHistory, punchInDate, today, creationDate);
  182. // 构建页面打卡结果
  183. PiTaskHistorySimpleVO piTaskHistorySimpleVO = new PiTaskHistorySimpleVO();
  184. piTaskHistorySimpleVO.setPunchInDate(punchInDateStr);
  185. piTaskHistorySimpleVO.setPunchInResult(punchInResultView);
  186. return piTaskHistorySimpleVO;
  187. }).collect(Collectors.toList());
  188. piTaskSimpleVO.setPiTaskHistorySimpleVOS(piTaskHistorySimpleVOS);
  189. return piTaskSimpleVO;
  190. }).collect(Collectors.toList());
  191. }
  192. /**
  193. * 判断页面打卡结果
  194. * @return
  195. */
  196. private PunchInResultViewEnum judgePunchInResultView(PiTask piTask, PiTaskHistory piTaskHistory, LocalDate punchInDate, LocalDate today, LocalDate creationDate) {
  197. // 未到打卡时间
  198. if (punchInDate.isAfter(today)) {
  199. return PunchInResultViewEnum.FUTURE;
  200. }
  201. // 任务未创建,不需要打卡
  202. if (punchInDate.isBefore(creationDate)) {
  203. return PunchInResultViewEnum.NOT_NEED;
  204. }
  205. // 打卡日期在今天之前且存在打卡记录,则简单判断打卡结果即可
  206. if (punchInDate.isBefore(today) && Objects.nonNull(piTaskHistory)) {
  207. return PunchInResultEnum.DONE.equals(piTaskHistory.getPunchInResult()) ? PunchInResultViewEnum.DONE : PunchInResultViewEnum.UNDONE;
  208. }
  209. // 打卡日期在今天之前且不存在打卡记录,则有两种情况:1.那天不用打卡;2.那天需要打卡但是没打卡
  210. // TODO 这里应该要有打卡任务的每天记录才能精准判断,现在先简化一下用当前任务的重复频率进行判断
  211. if (punchInDate.isBefore(today) && Objects.isNull(piTaskHistory)) {
  212. // 判断是否需要打卡
  213. boolean repeatResult = punchInCoreManager.judgeRepeat(piTask, today);
  214. return repeatResult ? PunchInResultViewEnum.UNDONE : PunchInResultViewEnum.NOT_NEED;
  215. }
  216. // 打卡日期是今天,且不存在打卡记录,则有两种情况:1.今天不用打卡;2.今天需要打卡但是还没打卡
  217. if (punchInDate.isEqual(today)) {
  218. // 判断是否需要打卡
  219. boolean repeatResult = punchInCoreManager.judgeRepeat(piTask, today);
  220. // 今天不用无须打卡
  221. if (!repeatResult) {
  222. return PunchInResultViewEnum.NOT_NEED;
  223. }
  224. // 今天还没有打卡记录无法判断打卡结果
  225. if (Objects.isNull(piTaskHistory)) {
  226. return PunchInResultViewEnum.TODAY_UNKNOWN;
  227. }
  228. return PunchInResultEnum.DONE.equals(piTaskHistory.getPunchInResult()) ? PunchInResultViewEnum.DONE : PunchInResultViewEnum.TODAY_UNKNOWN;
  229. }
  230. // 不符合任何情况则是无需打卡
  231. return PunchInResultViewEnum.NOT_NEED;
  232. }
  233. @Override
  234. public PiTaskVO queryTask(Long id) {
  235. Assert.isNullInBusiness(id, "请传入待查询的任务ID");
  236. return Optional.ofNullable(piTaskService.getById(id)).map(piTask -> {
  237. PiTaskVO piTaskVO = new PiTaskVO();
  238. BeanUtils.copyProperties(piTask, piTaskVO);
  239. return piTaskVO;
  240. }).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
  241. }
  242. @Override
  243. @Transactional(rollbackFor = Exception.class)
  244. public void saveTask(PiTaskRequest request) {
  245. Assert.isNullInBusiness(request, "请传入任务信息");
  246. // 今天
  247. LocalDate today = LocalDate.now();
  248. // 当前用户ID
  249. Long currentUserId = UserUtils.getCurrentUserId();
  250. // 数据校验
  251. if (PunchInMethodEnum.TIMING.equals(request.getPunchInMethod())) {
  252. if (Objects.isNull(request.getCompareRule()) || Objects.isNull(request.getTimeTrack())) {
  253. throw BusinessException.fail("打卡类型:计时,比较规则和时间不能为空");
  254. }
  255. if (CommonEnableStatusEnum.ENABLED.equals(request.getHolidayStatus()) && Objects.isNull(request.getHolidayTimeTrack())) {
  256. throw BusinessException.fail("打卡类型:计时,启用了节假日奖励,节假日的时间不能为空");
  257. }
  258. }
  259. if (PunchInMethodEnum.COUNT.equals(request.getPunchInMethod())) {
  260. if (Objects.isNull(request.getCompareRule()) || Objects.isNull(request.getCountTrack())) {
  261. throw BusinessException.fail("打卡类型:计数,比较规则和次数不能为空");
  262. }
  263. if (CommonEnableStatusEnum.ENABLED.equals(request.getHolidayStatus()) && Objects.isNull(request.getHolidayCountTrack())) {
  264. throw BusinessException.fail("打卡类型:计数,启用了节假日奖励,节假日的次数不能为空");
  265. }
  266. }
  267. if (CommonEnableStatusEnum.ENABLED.equals(request.getFullAttendanceStatus()) && (Objects.isNull(request.getFullAttendancePeriod()) || Objects.isNull(request.getFullAttendanceFaultToleranceCnt()))) {
  268. throw BusinessException.fail("启用了全勤奖励,全勤周期和全勤容错次数不能为空");
  269. }
  270. if (RepeatCategoryEnum.CUSTOM.equals(request.getRepeatCategory()) && !StringUtils.hasText(request.getRepeatCustomDay())) {
  271. throw BusinessException.fail("自定义重复周期,自定义重复日不能为空");
  272. }
  273. if (PunchInExtraMethodEnum.FIXED.equals(request.getExtraMethod()) || PunchInExtraMethodEnum.INTERVAL.equals(request.getExtraMethod())) {
  274. if (PunchInMethodEnum.TIMING.equals(request.getPunchInMethod()) && Objects.isNull(request.getExtraTimeStep())) {
  275. throw BusinessException.fail("打卡类型:计时,额外奖励方式为固定或区间,额外奖励时间间隔不能为空");
  276. }
  277. if (PunchInExtraMethodEnum.FIXED.equals(request.getExtraMethod()) && Objects.isNull(request.getExtraPoints())) {
  278. throw BusinessException.fail("额外奖励方式为固定,额外奖励积分不能为空");
  279. }
  280. if (PunchInExtraMethodEnum.INTERVAL.equals(request.getExtraMethod()) && CollectionUtils.isEmpty(request.getTaskExtList())) {
  281. throw BusinessException.fail("额外奖励方式为区间,积分区间信息不能为空");
  282. }
  283. }
  284. if (CommonEnableStatusEnum.ENABLED.equals(request.getContinueStatus()) && (Objects.isNull(request.getGraceDay()) || Objects.isNull(request.getContinueInterruptedCount()) || Objects.isNull(request.getPenaltyDay()))) {
  285. throw BusinessException.fail("启用了连续规则,宽限期、连续中断次数、惩罚天数不能为空");
  286. }
  287. if (CommonEnableStatusEnum.ENABLED.equals(request.getTaskPointsStatus()) && CollectionUtils.isEmpty(request.getContinueTaskExtList())) {
  288. throw BusinessException.fail("启用了任务积分计算,连续任务积分区间信息不能为空");
  289. }
  290. if (Objects.nonNull(request.getAutoArchiveDate()) && request.getAutoArchiveDate().isBefore(today)) {
  291. throw BusinessException.fail("任务自动归档时间无效,必需大于等于当前时间");
  292. }
  293. // 新增打卡任务
  294. PiTask addPiTask = new PiTask();
  295. BeanUtils.copyProperties(request, addPiTask);
  296. addPiTask.setId(null);
  297. addPiTask.setTaskStatus(VersionStatusEnum.ACTIVE);
  298. addPiTask.setTaskVersion(0);
  299. addPiTask.setArchiveStatus(ArchiveStatusEnum.ACTIVE);
  300. // 如果任务已存在,则更新任务版本号、设置唯一ID、旧数据归档
  301. if (Objects.nonNull(request.getId())) {
  302. PiTask piTask = Optional.ofNullable(piTaskService.getById(request.getId())).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
  303. addPiTask.setTaskVersion(piTask.getTaskVersion() + 1);
  304. addPiTask.setUniqueId(piTask.getUniqueId());
  305. // 旧数据归档
  306. PiTask updatePiTask = new PiTask();
  307. updatePiTask.setId(piTask.getId());
  308. updatePiTask.setTaskStatus(VersionStatusEnum.ARCHIVE);
  309. piTaskService.update(updatePiTask);
  310. }
  311. // 保存数据
  312. piTaskService.insert(addPiTask);
  313. // 如果首次保存,需要设置唯一ID
  314. if (Objects.isNull(addPiTask.getUniqueId())) {
  315. PiTask updatePiTask = new PiTask();
  316. updatePiTask.setId(addPiTask.getId());
  317. updatePiTask.setUniqueId(addPiTask.getId());
  318. piTaskService.update(updatePiTask);
  319. PiStatus addPiStatus = new PiStatus();
  320. addPiStatus.setUserId(currentUserId);
  321. addPiStatus.setTaskUniqueId(addPiTask.getId());
  322. addPiStatus.setStatusDate(today.toString());
  323. addPiStatus.setTaskContinueStatus(ContinueStatusEnum.INTERRUPTED);
  324. addPiStatus.setTaskContinueDay(0);
  325. addPiStatus.setRepeatCategory(addPiTask.getRepeatCategory());
  326. addPiStatus.setRepeatCustomDay(addPiTask.getRepeatCustomDay());
  327. piStatusService.insert(addPiStatus);
  328. }
  329. // 不是新增打卡任务 更新打卡记录
  330. if (Objects.nonNull(addPiTask.getUniqueId())) {
  331. // 查询今天的打卡记录
  332. PiTaskHistory oldPiTaskHistory = piTaskHistoryService.getByTaskUniqueIdAndPunchInDate(addPiTask.getUniqueId(), today.toString());
  333. // 已打卡则重新判断打卡状态
  334. if (Objects.nonNull(oldPiTaskHistory)) {
  335. // 新编辑的打卡任务+旧的打卡记录判断打卡结果
  336. PunchInResultEnum punchInResult = punchInCoreManager.judgePunchInResultInTask(addPiTask, oldPiTaskHistory);
  337. // 更新
  338. PiTaskHistory updatePiTaskHistory = new PiTaskHistory();
  339. updatePiTaskHistory.setId(oldPiTaskHistory.getId());
  340. updatePiTaskHistory.setPunchInResult(punchInResult);
  341. piTaskHistoryService.update(updatePiTaskHistory);
  342. }
  343. }
  344. // 清除缓存
  345. clearCache(currentUserId, false, false);
  346. }
  347. @Override
  348. @Transactional(rollbackFor = Exception.class)
  349. public void deleteTask(Long id) {
  350. Assert.isNullInBusiness(id, "请传入待删除的任务");
  351. // 新增打卡任务记录
  352. PiTask addPiTask = copyAndArchivePiTask(id);
  353. addPiTask.setTaskStatus(VersionStatusEnum.DELETE);
  354. piTaskService.insert(addPiTask);
  355. // 清除缓存
  356. clearCache(UserUtils.getCurrentUserId(), false, false);
  357. }
  358. @Override
  359. @Transactional(rollbackFor = Exception.class)
  360. public void doPunchIn(PunchInRequest request) {
  361. if (Objects.isNull(request) || Objects.isNull(request.getId())) {
  362. BusinessException.throwFail("请传入待打卡的任务");
  363. }
  364. // 当前用户ID
  365. Long currentUserId = UserUtils.getCurrentUserId();
  366. PiTask piTask = Optional.ofNullable(piTaskService.getById(request.getId())).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
  367. if (PunchInMethodEnum.TIMING.equals(piTask.getPunchInMethod()) && Objects.isNull(request.getTimeTrack())) {
  368. BusinessException.throwFail("打卡类型:计时,请传入时间记录");
  369. }
  370. // 打卡日期
  371. String punchInDate = LocalDate.now().toString();
  372. // 查询今天的打卡记录
  373. PiTaskHistory oldPiTaskHistory = piTaskHistoryService.getByTaskUniqueIdAndPunchInDate(piTask.getUniqueId(), punchInDate);
  374. // 待更新/新增的打卡记录
  375. PiTaskHistory tempPiTaskHistory = new PiTaskHistory();
  376. tempPiTaskHistory.setTaskId(piTask.getId());
  377. // 不存在则新增,存在则更新
  378. if (Objects.isNull(oldPiTaskHistory)) {
  379. tempPiTaskHistory.setTaskUniqueId(piTask.getUniqueId());
  380. tempPiTaskHistory.setPunchInDate(punchInDate);
  381. tempPiTaskHistory.setUserId(currentUserId);
  382. } else {
  383. tempPiTaskHistory.setId(oldPiTaskHistory.getId());
  384. }
  385. // 打卡类型:单次打卡,
  386. if (PunchInMethodEnum.SINGLE.equals(piTask.getPunchInMethod())) {
  387. // 需要判断是否重复打卡
  388. if (Objects.nonNull(oldPiTaskHistory) && Optional.ofNullable(oldPiTaskHistory.getCountTrack()).orElse(0) > 0) {
  389. BusinessException.throwFail("已打卡,无须重复打卡");
  390. }
  391. // 默认打卡次数是1,是计数打卡的特列
  392. tempPiTaskHistory.setCountTrack(1);
  393. }
  394. // 打卡类型:计数,需要累加打卡次数
  395. if (PunchInMethodEnum.COUNT.equals(piTask.getPunchInMethod())) {
  396. if (Objects.isNull(oldPiTaskHistory)) {
  397. tempPiTaskHistory.setCountTrack(1);
  398. } else {
  399. tempPiTaskHistory.setCountTrack(Optional.ofNullable(oldPiTaskHistory.getCountTrack()).orElse(0) + 1);
  400. }
  401. }
  402. // 打卡类型:计时,需要记录最新打卡时长
  403. if (PunchInMethodEnum.TIMING.equals(piTask.getPunchInMethod())) {
  404. tempPiTaskHistory.setTimeTrack(request.getTimeTrack());
  405. }
  406. // 设置打卡结果
  407. PunchInResultEnum punchInResult = punchInCoreManager.judgePunchInResultInTask(piTask, tempPiTaskHistory);
  408. tempPiTaskHistory.setPunchInResult(punchInResult);
  409. // 新增或更新
  410. if (Objects.isNull(oldPiTaskHistory)) {
  411. piTaskHistoryService.insert(tempPiTaskHistory);
  412. } else {
  413. piTaskHistoryService.update(tempPiTaskHistory);
  414. }
  415. // 清楚缓存
  416. clearCache(currentUserId, true, true);
  417. }
  418. @Override
  419. @Transactional(rollbackFor = Exception.class)
  420. public void archiveTask(Long id) {
  421. Assert.isNullInBusiness(id, "请传入待删除的任务");
  422. // 归档
  423. PiTask addPiTask = copyAndArchivePiTask(id);
  424. addPiTask.setArchiveStatus(ArchiveStatusEnum.ARCHIVE);
  425. addPiTask.setManualArchiveDate(LocalDate.now());
  426. piTaskService.insert(addPiTask);
  427. // 清楚缓存
  428. clearCache(UserUtils.getCurrentUserId(), false, false);
  429. }
  430. @Override
  431. public void revokePunchIn(Long id) {
  432. // 获取打卡任务
  433. PiTask piTask = Optional.ofNullable(piTaskService.getById(id)).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
  434. // 获取打卡记录
  435. PiTaskHistory piTaskHistory = piTaskHistoryService.getByTaskUniqueIdAndPunchInDate(piTask.getUniqueId(), LocalDate.now().toString());
  436. if (Objects.isNull(piTaskHistory)) {
  437. return;
  438. }
  439. PiTaskHistory updatePiTaskHistory = new PiTaskHistory();
  440. updatePiTaskHistory.setId(piTaskHistory.getId());
  441. updatePiTaskHistory.setTaskId(piTask.getId());
  442. // 单次打卡,次数置为0
  443. if (PunchInMethodEnum.SINGLE.equals(piTask.getPunchInMethod())) {
  444. updatePiTaskHistory.setCountTrack(0);
  445. }
  446. // 计时打卡 时间置为00:00:00.000
  447. if (PunchInMethodEnum.TIMING.equals(piTask.getPunchInMethod())) {
  448. updatePiTaskHistory.setTimeTrack(ZERO_TIME);
  449. }
  450. // 计数打卡,当前打卡次数减1
  451. if (PunchInMethodEnum.COUNT.equals(piTask.getPunchInMethod())) {
  452. Integer countTrack = Optional.ofNullable(piTaskHistory.getCountTrack()).orElse(0);
  453. if (countTrack == 0) {
  454. return;
  455. }
  456. updatePiTaskHistory.setCountTrack(countTrack - 1);
  457. }
  458. // 设置打卡结果
  459. PunchInResultEnum punchInResult = punchInCoreManager.judgePunchInResultInTask(piTask, updatePiTaskHistory);
  460. updatePiTaskHistory.setPunchInResult(punchInResult);
  461. // 更新
  462. piTaskHistoryService.update(updatePiTaskHistory);
  463. // 清楚缓存
  464. clearCache(UserUtils.getCurrentUserId(), true, true);
  465. }
  466. @Override
  467. // @Cacheable(cacheNames = CacheNameConstant.TASK_STAT, key = "T(com.punchsettle.server.utiis.UserUtils).getCurrentUserId()+'_'+#query.taskId+'_'+#query.statTime", condition = "#query.taskId != null && #query.statTime != null && !#query.statTime.isBlank()")
  468. public PiTaskStatVO queryStat(PiTaskStatQuery query) {
  469. // 当前用户ID
  470. Long currentUserId = UserUtils.getCurrentUserId();
  471. // 查找打卡任务
  472. PiTask piTask = Optional.ofNullable(piTaskService.getById(query.getTaskId())).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
  473. // 构造查询条件
  474. StatPiTaskQuery statPiTaskQuery = new StatPiTaskQuery();
  475. statPiTaskQuery.setUserId(currentUserId);
  476. statPiTaskQuery.setTaskUniqueId(piTask.getUniqueId());
  477. statPiTaskQuery.setStatTime(query.getStatTime());
  478. // 查询统计数据
  479. StatPiTask statPiTask = null;
  480. if (StatPeriodEnum.YEAR.equals(query.getStatPeriod())) {
  481. statPiTask = statPiTaskYearService.queryOneByCondition(statPiTaskQuery);
  482. } else if (StatPeriodEnum.MONTH.equals(query.getStatPeriod())) {
  483. statPiTask = statPiTaskMonthService.queryOneByCondition(statPiTaskQuery);
  484. }
  485. if (Objects.isNull(statPiTask)) {
  486. return null;
  487. }
  488. // 查询打卡记录
  489. PiTaskHistoryQuery piTaskHistoryQuery = new PiTaskHistoryQuery();
  490. piTaskHistoryQuery.setUserIds(Arrays.asList(currentUserId));
  491. piTaskHistoryQuery.setPunchInDateLike(query.getStatTime());
  492. List<PiTaskHistory> piTaskHistories = piTaskHistoryService.queryByCondition(piTaskHistoryQuery);
  493. // 查询打卡任务
  494. Set<Long> piTaskIds = piTaskHistories.stream().map(PiTaskHistory::getTaskId).collect(Collectors.toSet());
  495. PiTaskQuery piTaskQuery = new PiTaskQuery();
  496. piTaskQuery.setIds(piTaskIds);
  497. List<PiTask> piTasks = piTaskService.queryByCondition(piTaskQuery);
  498. // 打卡任务ID-打卡任务对象关联
  499. Map<Long, PiTask> piTaskMap = piTasks.stream().collect(Collectors.toMap(PiTask::getId, Function.identity(), (key1, key2) -> key1));
  500. // 统计范围的第一天
  501. String statFirstTimeStr = null;
  502. if (StatPeriodEnum.MONTH.equals(query.getStatPeriod())) {
  503. String[] split = query.getStatTime().split("-");
  504. statFirstTimeStr = String.format("%s-%s-01", split[0], split[1]);
  505. }
  506. LocalDate statFirstTime = LocalDate.parse(statFirstTimeStr);
  507. List<LocalDate> dateRangeInMonth = DateUtils.getDateRange(statFirstTime, LocalDate.now());
  508. DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("MM-dd");
  509. String[] categories = dateRangeInMonth.stream().map(v -> dateTimeFormatter.format(v)).toArray(String[]::new);
  510. Integer[] lineSeriesData = piTaskHistories.stream().map(PiTaskHistory::getCountTrack).toArray(Integer[]::new);
  511. LineSeriesVO lineSeriesVO = new LineSeriesVO();
  512. lineSeriesVO.setName("每日打卡次数");
  513. lineSeriesVO.setData(lineSeriesData);
  514. LineVO lineVO = new LineVO();
  515. lineVO.setCategories(categories);
  516. lineVO.setSeries(Arrays.asList(lineSeriesVO));
  517. // 组装数据
  518. List<PiTaskHistoryStatVO> piTaskHistoryStatVOS = piTaskHistories.stream().map(piTaskHistory -> {
  519. PiTaskHistoryStatVO piTaskHistoryStatVO = new PiTaskHistoryStatVO();
  520. BeanUtils.copyProperties(piTaskHistory, piTaskHistoryStatVO);
  521. return piTaskHistoryStatVO;
  522. }).collect(Collectors.toList());
  523. PiTaskStatVO piTaskStatVO = new PiTaskStatVO();
  524. BeanUtils.copyProperties(statPiTask, piTaskStatVO);
  525. piTaskStatVO.setPiTaskHistoryStatVOS(piTaskHistoryStatVOS);
  526. piTaskStatVO.setLineVO(lineVO);
  527. return piTaskStatVO;
  528. }
  529. /**
  530. * 复制打卡任务信息并把旧的信息归档(审计信息不复制、任务版本号自动加1,任务状态:活跃)
  531. * @param id 打卡任务id
  532. * @return
  533. */
  534. private PiTask copyAndArchivePiTask(Long id) {
  535. // 获取旧的打卡任务信息
  536. PiTask oldPiTask = Optional.ofNullable(piTaskService.getById(id)).orElseThrow(() -> BusinessException.fail("打卡任务不存在"));
  537. // 旧的打卡任务信息归档
  538. PiTask updatePiTask = new PiTask();
  539. updatePiTask.setId(oldPiTask.getId());
  540. updatePiTask.setTaskStatus(VersionStatusEnum.ARCHIVE);
  541. piTaskService.update(updatePiTask);
  542. // 新的打卡任务信息
  543. PiTask piTask = new PiTask();
  544. BeanUtils.copyProperties(oldPiTask, piTask);
  545. piTask.setId(null);
  546. piTask.setTaskStatus(VersionStatusEnum.ACTIVE);
  547. piTask.setTaskVersion(Optional.ofNullable(oldPiTask.getTaskVersion()).orElse(0) + 1);
  548. return piTask;
  549. }
  550. @Override
  551. @Cacheable(cacheNames = CacheNameConstant.TASK_PUNCH_IN_HISTORY, key = "T(com.punchsettle.server.utiis.UserUtils).getCurrentUserId()+'_'+#punchInDate", condition = "#punchInDate != null && !#punchInDate.isBlank()")
  552. public List<PiTaskHistoryStatVO> queryPunchInHistory(String punchInDate) {
  553. Assert.isNullInBusiness(punchInDate, "打卡日期不能为空");
  554. // 查询打卡记录
  555. PiTaskHistoryQuery piTaskHistoryQuery = new PiTaskHistoryQuery();
  556. piTaskHistoryQuery.setUserIds(Arrays.asList(UserUtils.getCurrentUserId()));
  557. piTaskHistoryQuery.setPunchInDate(punchInDate);
  558. List<PiTaskHistory> piTaskHistories = piTaskHistoryService.queryByCondition(piTaskHistoryQuery);
  559. if (CollectionUtils.isEmpty(piTaskHistories)) {
  560. return List.of();
  561. }
  562. return piTaskHistories.stream().map(piTaskHistory -> {
  563. PiTaskHistoryStatVO piTaskHistoryStatVO = new PiTaskHistoryStatVO();
  564. BeanUtils.copyProperties(piTaskHistory, piTaskHistoryStatVO);
  565. return piTaskHistoryStatVO;
  566. }).collect(Collectors.toList());
  567. }
  568. @Override
  569. @Cacheable(cacheNames = CacheNameConstant.TASK_LIST_FOR_USER, key = "#userId", condition = "#userId != null")
  570. public List<PiTask> getTaskListInCache(Long userId) {
  571. Assert.isNullInBusiness(userId, "用户ID不能为空");
  572. return piTaskService.getActiveTask(Arrays.asList(userId));
  573. }
  574. @Override
  575. @Cacheable(cacheNames = CacheNameConstant.TASK_HISTORY_LIST_FOR_USER, key = "#userId+'_'+#punchInDate", condition = "#userId != null && #punchInDate != null && !#punchInDate.isBlank()")
  576. public List<PiTaskHistory> getTaskHistoryListInCache(Long userId, String punchInDate) {
  577. Assert.isNullInBusiness(userId, "用户ID不能为空");
  578. Assert.isNullInBusiness(punchInDate, "打卡日期不能为空");
  579. PiTaskHistoryQuery piTaskHistoryQuery = new PiTaskHistoryQuery();
  580. piTaskHistoryQuery.setPunchInDate(punchInDate);
  581. piTaskHistoryQuery.setUserIds(Arrays.asList(userId));
  582. List<PiTaskHistory> piTaskHistories = piTaskHistoryService.queryByCondition(piTaskHistoryQuery);
  583. return piTaskHistories;
  584. }
  585. @Override
  586. public void clearCache(Long userId, boolean deleteHistory, boolean deleteStat) {
  587. cacheManager.batchEvict(Arrays.asList(CacheNameConstant.TASK_LIST_FOR_USER, CacheNameConstant.TASK_LIST_VO), userId);
  588. if (deleteHistory) {
  589. cacheManager.batchEvictLike(Arrays.asList(CacheNameConstant.TASK_PUNCH_IN_HISTORY, CacheNameConstant.TASK_HISTORY_LIST_FOR_USER), String.valueOf(userId));
  590. }
  591. if (deleteStat) {
  592. cacheManager.batchEvictLike(Arrays.asList(CacheNameConstant.TASK_STAT), String.valueOf(userId));
  593. }
  594. }
  595. }