PunchInManagerImpl.java 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797
  1. package com.punchsettle.server.service.manager.impl;
  2. import java.math.BigDecimal;
  3. import java.math.RoundingMode;
  4. import java.text.SimpleDateFormat;
  5. import java.time.LocalDate;
  6. import java.time.LocalTime;
  7. import java.time.YearMonth;
  8. import java.time.temporal.ChronoUnit;
  9. import java.util.ArrayList;
  10. import java.util.Arrays;
  11. import java.util.Comparator;
  12. import java.util.List;
  13. import java.util.Map;
  14. import java.util.Objects;
  15. import java.util.Optional;
  16. import java.util.function.Function;
  17. import java.util.stream.Collectors;
  18. import com.punchsettle.server.atomic.entity.PunchInMultiTask;
  19. import com.punchsettle.server.atomic.entity.PunchInMultiTaskExt;
  20. import com.punchsettle.server.atomic.entity.PunchInMultiTaskHistory;
  21. import com.punchsettle.server.atomic.entity.PunchInStatsMonth;
  22. import com.punchsettle.server.atomic.entity.PunchInStatsWeek;
  23. import com.punchsettle.server.atomic.entity.PunchInStatus;
  24. import com.punchsettle.server.atomic.entity.PunchInTaskExt;
  25. import com.punchsettle.server.constant.ConsecutiveStatusEnum;
  26. import com.punchsettle.server.constant.FullAttendancePeriodEnum;
  27. import com.punchsettle.server.constant.PunchInDimensionEnum;
  28. import com.punchsettle.server.constant.PunchInExtraMethodEnum;
  29. import com.punchsettle.server.constant.PunchInMethodMultiEnum;
  30. import com.punchsettle.server.constant.RepeatCategoryEnum;
  31. import com.punchsettle.server.service.manager.ICalendarManager;
  32. import org.springframework.beans.BeanUtils;
  33. import org.springframework.beans.factory.annotation.Autowired;
  34. import org.springframework.stereotype.Service;
  35. import org.springframework.transaction.annotation.Transactional;
  36. import org.springframework.util.CollectionUtils;
  37. import com.punchsettle.server.atomic.entity.PunchIn;
  38. import com.punchsettle.server.atomic.entity.PunchInRecord;
  39. import com.punchsettle.server.atomic.entity.PunchInTask;
  40. import com.punchsettle.server.atomic.entity.PunchInTaskHistory;
  41. import com.punchsettle.server.atomic.service.IPunchInRecordService;
  42. import com.punchsettle.server.atomic.service.IPunchInService;
  43. import com.punchsettle.server.common.exception.BusinessException;
  44. import com.punchsettle.server.common.utils.Assert;
  45. import com.punchsettle.server.common.constant.CommonEnableStatusEnum;
  46. import com.punchsettle.server.constant.CompareRuleEnum;
  47. import com.punchsettle.server.constant.PunchInCategoryEnum;
  48. import com.punchsettle.server.constant.PunchInMethodEnum;
  49. import com.punchsettle.server.constant.PunchInSettleTypeEnum;
  50. import com.punchsettle.server.constant.PunchInStatusEnum;
  51. import com.punchsettle.server.constant.PunchInStatusV1Enum;
  52. import com.punchsettle.server.constant.PunchInStatusViewEnum;
  53. import com.punchsettle.server.pojo.punchin.PunchInCalendarDataVO;
  54. import com.punchsettle.server.pojo.punchin.PunchInDataQuery;
  55. import com.punchsettle.server.pojo.punchin.PunchInDataVO;
  56. import com.punchsettle.server.pojo.punchin.PunchInQuery;
  57. import com.punchsettle.server.pojo.punchin.PunchInRecordDataVO;
  58. import com.punchsettle.server.pojo.punchin.PunchInRecordQuery;
  59. import com.punchsettle.server.pojo.punchin.PunchInRecordRequest;
  60. import com.punchsettle.server.pojo.punchin.PunchInRecordVO;
  61. import com.punchsettle.server.pojo.punchin.PunchInRequest;
  62. import com.punchsettle.server.pojo.punchin.PunchInVO;
  63. import com.punchsettle.server.pojo.punchin.PunchInWithRecordVO;
  64. import com.punchsettle.server.service.manager.IPunchInManager;
  65. import com.punchsettle.server.service.manager.ISettleManager;
  66. import com.punchsettle.server.utiis.DateUtils;
  67. import com.punchsettle.server.utiis.UserUtils;
  68. import lombok.extern.slf4j.Slf4j;
  69. import org.springframework.util.StringUtils;
  70. /**
  71. * @author tyuio
  72. * @version 1.0.0
  73. * @description 打卡结算任务 服务类
  74. * @date 2024/11/25 15:12
  75. */
  76. @Slf4j
  77. @Service
  78. public class PunchInManagerImpl implements IPunchInManager {
  79. @Autowired
  80. private IPunchInService punchInService;
  81. @Autowired
  82. private IPunchInRecordService punchInRecordService;
  83. @Autowired
  84. private ISettleManager settleManager;
  85. /**
  86. * 数值100
  87. */
  88. private static final BigDecimal ONE_HUNDRED = new BigDecimal(100);
  89. @Override
  90. public List<PunchInWithRecordVO> queryPunchInAndRecord() {
  91. // 获取当前用户ID
  92. Long currentUserId = Optional.ofNullable(UserUtils.getCurrentUserId())
  93. .orElseThrow(() -> BusinessException.fail("无法获取当前用户信息,无法查询打卡任务"));
  94. // 查询打卡任务
  95. PunchInQuery punchInQuery = new PunchInQuery();
  96. punchInQuery.setArchiveFlag(false);
  97. punchInQuery.setUserIds(Arrays.asList(currentUserId));
  98. List<PunchIn> punchIns = punchInService.listByCondition(punchInQuery);
  99. if (CollectionUtils.isEmpty(punchIns)) {
  100. log.info("用户:{} 没有查询到生效的打卡任务");
  101. return List.of();
  102. }
  103. // 获取一周的起始日期范围
  104. List<LocalDate> weeklyDateRange = DateUtils.getWeeklyDateRange();
  105. // 获取打卡任务ID
  106. List<Long> punchInIds = punchIns.stream().map(PunchIn::getId).collect(Collectors.toList());
  107. // 找出范围内的打卡记录
  108. PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
  109. punchInRecordQuery.setPunchInIds(punchInIds);
  110. punchInRecordQuery.setStartDate(weeklyDateRange.getFirst().toString());
  111. punchInRecordQuery.setEndDate(weeklyDateRange.getLast().toString());
  112. List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(punchInRecordQuery);
  113. // 打卡任务-打卡记录 分组
  114. Map<Long, List<PunchInRecord>> recordMap =
  115. punchInRecords.stream().collect(Collectors.groupingBy(PunchInRecord::getPunchInId));
  116. // 日期格式化
  117. SimpleDateFormat sdf = DateUtils.buildDateFormat();
  118. // 当前日期
  119. LocalDate today = LocalDate.now();
  120. // 构建打卡记录
  121. List<PunchInWithRecordVO> punchInWithRecordVOS = new ArrayList<>();
  122. // 初始化的时间记录
  123. LocalTime initRecordTimeTrack = LocalTime.parse("00:00:00.000");
  124. for (PunchIn punchIn : punchIns) {
  125. // 一周的打卡记录容器
  126. List<PunchInRecordVO> weeklyRecords = new ArrayList<>();
  127. // 打卡任务信息
  128. PunchInWithRecordVO punchInWithRecordVO = new PunchInWithRecordVO();
  129. BeanUtils.copyProperties(punchIn, punchInWithRecordVO);
  130. punchInWithRecordVO.setPunchInId(punchIn.getId());
  131. punchInWithRecordVO.setPunchInRecords(weeklyRecords);
  132. punchInWithRecordVO.setRecordTimeTrack(initRecordTimeTrack);
  133. punchInWithRecordVO.setRecordCountTrack(0);
  134. punchInWithRecordVOS.add(punchInWithRecordVO);
  135. // 打卡任务创建日期
  136. LocalDate punchInCreationDate = LocalDate.parse(sdf.format(punchIn.getCreationTime()));
  137. // 获取打卡任务对应的打卡记录,并转为打卡日期-打卡记录map
  138. List<PunchInRecord> records =
  139. Optional.ofNullable(recordMap.get(punchInWithRecordVO.getPunchInId())).orElse(List.of());
  140. Map<String, PunchInRecord> weeklyRecordMap = records.stream()
  141. .collect(Collectors.toMap(PunchInRecord::getPunchInDate, Function.identity(), (key1, key2) -> key1));
  142. for (LocalDate weeklyDate : weeklyDateRange) {
  143. String weeklyDateStr = weeklyDate.toString();
  144. // 获取打卡记录
  145. PunchInRecord punchInRecord = weeklyRecordMap.get(weeklyDateStr);
  146. // 根据过往打卡记录设置打卡状态
  147. PunchInStatusViewEnum punchInStatus =
  148. judgePunchInStatus(today, weeklyDate, punchInCreationDate, punchIn, punchInRecord);
  149. // 设置打卡记录
  150. PunchInRecordVO punchInRecordVO = new PunchInRecordVO();
  151. punchInRecordVO.setPunchInDate(weeklyDateStr);
  152. punchInRecordVO.setPunchInStatus(punchInStatus);
  153. weeklyRecords.add(punchInRecordVO);
  154. // 如果是今天的打卡记录,设置计数/计时属性
  155. if (!Objects.isNull(punchInRecord) && today.isEqual(weeklyDate)) {
  156. punchInWithRecordVO.setRecordTimeTrack(punchInRecord.getTimeTrack());
  157. punchInWithRecordVO.setRecordCountTrack(punchInRecord.getCountTrack());
  158. }
  159. // 如果是今天设置状态控制页面显示
  160. if (today.isEqual(weeklyDate)) {
  161. punchInWithRecordVO.setPunchInStatus(punchInStatus);
  162. }
  163. }
  164. }
  165. return punchInWithRecordVOS;
  166. }
  167. private PunchInStatusViewEnum judgePunchInStatus(LocalDate today, LocalDate weeklyDate,
  168. LocalDate punchInCreationDate, PunchIn punchIn, PunchInRecord punchInRecord) {
  169. // 一周某天还没到,无法打卡
  170. if (weeklyDate.isAfter(today)) {
  171. return PunchInStatusViewEnum.FUTURE_TIME;
  172. }
  173. // 一周某天早于任务创建时间,无法打卡
  174. if (weeklyDate.isBefore(punchInCreationDate)) {
  175. return PunchInStatusViewEnum.UNCREATED;
  176. }
  177. // 一周某天在今天之前存在打卡记录,则需要判断是否完成打卡
  178. if (!Objects.isNull(punchInRecord) && weeklyDate.isBefore(today)) {
  179. if (PunchInStatusV1Enum.FINISH.equals(punchInRecord.getPunchInStatus())
  180. || PunchInStatusV1Enum.REMAKE_FINISH.equals(punchInRecord.getPunchInStatus())) {
  181. return PunchInStatusViewEnum.PUNCH_IN;
  182. }
  183. }
  184. // 一周的某天是今天,且存在打卡记录,则判断是否完成打卡
  185. if (weeklyDate.isEqual(today) && !Objects.isNull(punchInRecord)) {
  186. PunchInStatusV1Enum punchInStatusV1Enum = settleManager.judgePunchInStatus(punchIn, punchInRecord);
  187. if (PunchInStatusV1Enum.FINISH.equals(punchInStatusV1Enum)
  188. || PunchInStatusV1Enum.REMAKE_FINISH.equals(punchInStatusV1Enum)) {
  189. return PunchInStatusViewEnum.PUNCH_IN;
  190. }
  191. // 当天存在打卡记录,但是还没满足打卡条件,则认为打卡状态未知
  192. return PunchInStatusViewEnum.TODAY_UNKNOWN;
  193. }
  194. // 一周的某天是今天,且不存在打卡记录,则还没进行打卡
  195. if (weeklyDate.isEqual(today) && Objects.isNull(punchInRecord)) {
  196. return PunchInStatusViewEnum.TODAY_UNKNOWN;
  197. }
  198. // 不符合任何情况则是未打卡
  199. return PunchInStatusViewEnum.UN_PUNCH_IN;
  200. }
  201. @Override
  202. public PunchInVO queryPunchInById(Long punchInId) {
  203. Assert.isNullInBusiness(punchInId, "请传入待查询的任务ID");
  204. return Optional.ofNullable(punchInService.getById(punchInId)).map(punchIn -> {
  205. PunchInVO punchInVO = new PunchInVO();
  206. BeanUtils.copyProperties(punchIn, punchInVO);
  207. return punchInVO;
  208. }).orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
  209. }
  210. @Override
  211. public void saveOrUpdatePunchIn(PunchInRequest request) {
  212. Assert.isNullInBusiness(request, "请传入任务信息");
  213. if (PunchInCategoryEnum.COUNT.equals(request.getCategory())
  214. && (Objects.isNull(request.getRule()) || Objects.isNull(request.getCountTrack()))) {
  215. throw BusinessException.fail("打卡类型:计数,比较规则和次数不能为空");
  216. }
  217. if (PunchInCategoryEnum.TIME.equals(request.getCategory())
  218. && (Objects.isNull(request.getRule()) || Objects.isNull(request.getTimeTrack()))) {
  219. throw BusinessException.fail("打卡类型:计时,比较规则和时间不能为空");
  220. }
  221. PunchIn punchIn = new PunchIn();
  222. BeanUtils.copyProperties(request, punchIn);
  223. punchIn.setArchiveFlag(false);
  224. if (Objects.isNull(punchIn.getId())) {
  225. punchInService.insert(punchIn);
  226. } else {
  227. punchInService.update(punchIn);
  228. }
  229. }
  230. @Override
  231. public void deletePunchIn(Long punchInId) {
  232. Assert.isNullInBusiness(punchInId, "请传入待删除的任务");
  233. punchInService.delete(punchInId);
  234. }
  235. @Override
  236. @Transactional(rollbackFor = Exception.class)
  237. public void doPunchIn(PunchInRequest request) {
  238. if (Objects.isNull(request) || Objects.isNull(request.getId())) {
  239. BusinessException.throwFail("请传入待打卡的任务");
  240. }
  241. PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getId()))
  242. .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
  243. if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory()) && Objects.isNull(request.getTimeTrack())) {
  244. BusinessException.throwFail("打卡类型:计时,请传入时间记录");
  245. }
  246. // 查询今天的打卡记录
  247. LocalDate today = LocalDate.now();
  248. PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
  249. punchInRecordQuery.setPunchInDate(today.toString());
  250. punchInRecordQuery.setPunchInId(request.getId());
  251. PunchInRecord oldPunchInRecord = punchInRecordService.selectOneByCondition(punchInRecordQuery);
  252. // 打卡类型:单次打卡,需要判断是否重复打卡
  253. if (PunchInCategoryEnum.SINGLE.equals(punchIn.getCategory()) && !Objects.isNull(oldPunchInRecord)) {
  254. throw BusinessException.fail("已打卡,无须重复打卡");
  255. }
  256. // 获取或创建打卡记录
  257. PunchInRecord punchInRecord = Optional.ofNullable(oldPunchInRecord).orElseGet(() -> {
  258. PunchInRecord record = new PunchInRecord();
  259. record.setPunchInId(request.getId());
  260. record.setPunchInDate(LocalDate.now().toString());
  261. return record;
  262. });
  263. // 打卡类型:计数,需要累加打卡次数
  264. if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())) {
  265. punchInRecord.setCountTrack(Optional.ofNullable(punchInRecord.getCountTrack()).orElse(0) + 1);
  266. }
  267. // 打卡类型:计时,需要记录最新打卡时长
  268. if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
  269. punchInRecord.setTimeTrack(request.getTimeTrack());
  270. }
  271. // 新增或更新
  272. if (Objects.isNull(punchInRecord.getId())) {
  273. punchInRecordService.insert(punchInRecord);
  274. } else {
  275. punchInRecordService.update(punchInRecord);
  276. }
  277. }
  278. @Override
  279. public void archivePunchIn(Long punchInId) {
  280. Assert.isNullInBusiness(punchInId, "请选择待归档的任务");
  281. PunchIn punchIn = new PunchIn();
  282. punchIn.setId(punchInId);
  283. punchIn.setArchiveFlag(true);
  284. punchInService.update(punchIn);
  285. }
  286. @Override
  287. public void remakePunchIn(PunchInRecordRequest request) {
  288. // 待补卡的打卡日期
  289. LocalDate punchInDate = LocalDate.parse(request.getPunchInDate());
  290. // 如果补打卡日期是今天,则不允许补卡
  291. if (punchInDate.isEqual(LocalDate.now())) {
  292. BusinessException.throwFail("补打卡日期不能为今天");
  293. }
  294. // 获取打卡任务
  295. PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getPunchInId()))
  296. .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
  297. // 补打卡的打卡日期不能早于打卡任务创建日期
  298. if (punchInDate.isBefore(punchIn.getCreationTime().toLocalDateTime().toLocalDate())) {
  299. BusinessException.throwFail("补打卡日期不能早于打卡任务创建日期");
  300. }
  301. settleManager.settleHandler(PunchInSettleTypeEnum.REMAKE, punchInDate,
  302. Arrays.asList(UserUtils.getCurrentUserId()), Arrays.asList(request.getPunchInId()));
  303. }
  304. @Override
  305. public void revokePunchIn(PunchInRecordRequest request) {
  306. // 获取打卡任务
  307. PunchIn punchIn = Optional.ofNullable(punchInService.getById(request.getPunchInId()))
  308. .orElseThrow(() -> BusinessException.fail("无法查询到该打卡任务"));
  309. PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
  310. punchInRecordQuery.setPunchInId(request.getPunchInId());
  311. punchInRecordQuery.setPunchInDate(LocalDate.now().toString());
  312. PunchInRecord punchInRecord = punchInRecordService.selectOneByCondition(punchInRecordQuery);
  313. if (Objects.isNull(punchInRecord)) {
  314. log.info("打卡任务:{},打卡日期:{} 没有找到当天打卡记录,进行撤销", punchInRecordQuery.getPunchInId(),
  315. punchInRecordQuery.getPunchInDate());
  316. return;
  317. }
  318. // 单次打卡/计时打卡 直接删除记录即可
  319. if (PunchInCategoryEnum.SINGLE.equals(punchIn.getCategory())
  320. || PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
  321. punchInRecordService.delete(punchInRecord.getId());
  322. }
  323. // 计数打卡,需要减去打卡次数
  324. if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())
  325. && Optional.ofNullable(punchInRecord.getCountTrack()).orElse(0) > 0) {
  326. PunchInRecord updateRecord = new PunchInRecord();
  327. updateRecord.setId(punchInRecord.getId());
  328. updateRecord.setCountTrack(punchInRecord.getCountTrack() - 1);
  329. punchInRecordService.update(updateRecord);
  330. if (updateRecord.getCountTrack() == 0) {
  331. punchInRecordService.delete(punchInRecord.getId());
  332. }
  333. }
  334. }
  335. @Override
  336. public PunchInDataVO queryPunchInData(PunchInDataQuery query) {
  337. PunchIn punchIn = Optional.ofNullable(punchInService.getById(query.getId()))
  338. .orElseThrow(() -> BusinessException.fail("找到指定的打卡任务"));
  339. SimpleDateFormat sdf = DateUtils.buildDateFormat();
  340. YearMonth yearMonth = YearMonth.of(query.getYear(), query.getMonth());
  341. // 本月第一日
  342. LocalDate firstOfMonth = yearMonth.atDay(1);
  343. // 本月最后以日
  344. LocalDate endOfMonth = yearMonth.atEndOfMonth();
  345. // 任务创建日期
  346. LocalDate punchInCreationDate = punchIn.getCreationTime().toLocalDateTime().toLocalDate();
  347. // 获取打卡记录
  348. PunchInRecordQuery punchInRecordQuery = new PunchInRecordQuery();
  349. punchInRecordQuery.setPunchInId(punchIn.getId());
  350. punchInRecordQuery.setStartDate(String.format("%s 00:00:00.000", firstOfMonth));
  351. punchInRecordQuery.setEndDate(String.format("%s 23:59:59.999", endOfMonth));
  352. List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(punchInRecordQuery);
  353. // 构造数据,日历部分的数据只需要已打卡的数据
  354. List<PunchInCalendarDataVO> punchInCalendarDataVOS = new ArrayList<>();
  355. List<PunchInRecordDataVO> punchInRecordDtoList = new ArrayList<>();
  356. for (PunchInRecord punchInRecord : punchInRecords) {
  357. PunchInRecordDataVO punchInRecordDataVO = new PunchInRecordDataVO();
  358. BeanUtils.copyProperties(punchInRecord, punchInRecordDataVO);
  359. punchInRecordDtoList.add(punchInRecordDataVO);
  360. if (PunchInStatusV1Enum.UN_FINISH.equals(punchInRecord.getPunchInStatus())
  361. || PunchInStatusV1Enum.DOING.equals(punchInRecord.getPunchInStatus())) {
  362. continue;
  363. }
  364. PunchInCalendarDataVO punchInCalendarDataVO = new PunchInCalendarDataVO();
  365. punchInCalendarDataVO.setPunchInDate(punchInRecord.getPunchInDate());
  366. punchInCalendarDataVO.setInfo("打卡");
  367. punchInCalendarDataVO.setDate(punchInRecord.getPunchInDate());
  368. punchInCalendarDataVOS.add(punchInCalendarDataVO);
  369. }
  370. // 计算全勤率
  371. BigDecimal punchInRate = BigDecimal.ZERO;
  372. // 要考虑任务刚创建的情况,任务较迟创建的话则使用任务创建时间
  373. long dayLength = ChronoUnit.DAYS
  374. .between(firstOfMonth.isBefore(punchInCreationDate) ? punchInCreationDate : firstOfMonth, LocalDate.now());
  375. if (dayLength != 0) {
  376. punchInRate = BigDecimal.valueOf(punchInCalendarDataVOS.size())
  377. .divide(BigDecimal.valueOf(dayLength), 4, RoundingMode.HALF_DOWN).multiply(ONE_HUNDRED);
  378. }
  379. // 构造返回结果
  380. PunchInDataVO punchInDataVO = new PunchInDataVO();
  381. punchInDataVO.setStartDate(sdf.format(punchIn.getCreationTime()));
  382. punchInDataVO.setEndDate(LocalDate.now().toString());
  383. punchInDataVO.setPunchInNum(punchInCalendarDataVOS.size());
  384. punchInDataVO.setCalendarSelected(punchInCalendarDataVOS);
  385. punchInDataVO.setPunchInRecords(punchInRecordDtoList);
  386. punchInDataVO.setPunchInRate(punchInRate);
  387. return punchInDataVO;
  388. }
  389. /**
  390. * 默认打卡完成率
  391. */
  392. private static final BigDecimal DEFAULT_PUNCH_IN_DONE_RATE = new BigDecimal("101.00");
  393. @Autowired
  394. private ICalendarManager calendarManager;
  395. @Override
  396. public boolean judgeRepeat(PunchInTask punchInTask, String repeatDateStr) {
  397. // 每天
  398. if (RepeatCategoryEnum.EVERYDAY.equals(punchInTask.getRepeatCategory())) {
  399. return true;
  400. }
  401. // 法定工作日
  402. if (RepeatCategoryEnum.WORKDAY.equals(punchInTask.getRepeatCategory())) {
  403. return !calendarManager.judgeHoliday(repeatDateStr);
  404. }
  405. // 法定节假日(含周末)
  406. if (RepeatCategoryEnum.HOLIDAY.equals(punchInTask.getRepeatCategory())) {
  407. return !calendarManager.judgeHoliday(repeatDateStr);
  408. }
  409. // 自定义
  410. if (RepeatCategoryEnum.CUSTOM.equals(punchInTask.getRepeatCategory())) {
  411. if (!StringUtils.hasText(punchInTask.getRepeatCustomDay())) {
  412. BusinessException.throwFail(String.format("打卡任务ID:%s, 重复周期类型:自定义重复,没有设定自定义重复日期"));
  413. }
  414. LocalDate repeatDate = LocalDate.parse(repeatDateStr);
  415. int dayOfWeekValue = repeatDate.getDayOfWeek().getValue();
  416. return punchInTask.getRepeatCustomDay().contains(String.valueOf(dayOfWeekValue));
  417. }
  418. return false;
  419. }
  420. @Override
  421. public PunchInStatusEnum judgePunchInStatusInTask(PunchInTask punchInTask, PunchInTaskHistory punchInTaskHistory, boolean holidayFlag) {
  422. Assert.isNullInBusiness(punchInTask, "打卡任务不能为空 ");
  423. // 没有打卡记录,直接没完成
  424. if (Objects.isNull(punchInTaskHistory)) {
  425. return PunchInStatusEnum.UNDONE;
  426. }
  427. // 单次打卡,不区分是否节假日
  428. if (PunchInMethodEnum.SINGLE.equals(punchInTask.getPunchInMethod())) {
  429. int countTrack = Optional.ofNullable(punchInTask.getCountTrack()).orElse(0);
  430. return countTrack > 0 ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE;
  431. }
  432. // 打卡任务的节假日启用配置
  433. CommonEnableStatusEnum holidayStatus = Optional.ofNullable(punchInTask.getHolidayStatus()).orElse(CommonEnableStatusEnum.DISABLED);
  434. boolean enableHolidayFlag = CommonEnableStatusEnum.ENABLED.equals(holidayStatus) && holidayFlag;
  435. // 计数打卡,要区分是否节假日使用不同的判断标准
  436. if (PunchInMethodEnum.COUNT.equals(punchInTask.getPunchInMethod())) {
  437. int countTrack = enableHolidayFlag ? punchInTask.getHolidayCountTrack() : punchInTask.getCountTrack();
  438. int punchInCountTrack = Optional.ofNullable(punchInTaskHistory.getCountTrack()).orElse(0);
  439. if (CompareRuleEnum.GTE.equals(punchInTask.getCompareRule()) && punchInCountTrack >= countTrack) {
  440. return PunchInStatusEnum.DONE;
  441. }
  442. if (CompareRuleEnum.LTE.equals(punchInTask.getCompareRule()) && punchInCountTrack <= countTrack) {
  443. return PunchInStatusEnum.DONE;
  444. }
  445. return PunchInStatusEnum.UNDONE;
  446. }
  447. // 计时打卡,要区分是否节假日使用不同的判断标准
  448. if (PunchInCategoryEnum.TIME.equals(punchInTask.getPunchInMethod())) {
  449. LocalTime timeTrack = enableHolidayFlag ? punchInTask.getHolidayTimeTrack() : punchInTask.getTimeTrack();
  450. LocalTime punchInTimeTrack = Optional.ofNullable(punchInTaskHistory.getTimeTrack()).orElse(LocalTime.parse("00:00:00.000"));
  451. if (CompareRuleEnum.GTE.equals(punchInTask.getCompareRule()) && punchInTimeTrack.compareTo(timeTrack) > -1) {
  452. return PunchInStatusEnum.DONE;
  453. }
  454. if (CompareRuleEnum.LTE.equals(punchInTask.getCompareRule()) && punchInTimeTrack.compareTo(timeTrack) < 1) {
  455. return PunchInStatusEnum.DONE;
  456. }
  457. return PunchInStatusEnum.UNDONE;
  458. }
  459. return PunchInStatusEnum.UNDONE;
  460. }
  461. @Override
  462. public PunchInStatusEnum judgePunchInStatusInMultiTask(PunchInMultiTask punchInMultiTask, List<PunchInTaskHistory> punchInTaskHistoryList, int multiTaskNum) {
  463. if (CollectionUtils.isEmpty(punchInTaskHistoryList) || multiTaskNum == 0) {
  464. return PunchInStatusEnum.UNDONE;
  465. }
  466. // 完成的打卡数量
  467. long punchInDoneCount = punchInTaskHistoryList.stream().filter(v -> PunchInStatusEnum.DONE.equals(v.getPunchInStatus())).count();
  468. // 次数,当天打卡次数大于等于设置的次数,则完成打卡
  469. if (PunchInMethodMultiEnum.COUNT.equals(punchInMultiTask.getPunchInMethod())) {
  470. // 没有设置时默认设置一个较大的数,避免出现打卡次数为0的情况
  471. Integer doneCount = Optional.ofNullable(punchInMultiTask.getPunchInDoneCount()).orElse(punchInTaskHistoryList.size() + 1);
  472. return punchInDoneCount >= doneCount ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE;
  473. }
  474. // 比率,当天打卡完成比率大于等于设置的比率,则完成打卡
  475. if (PunchInMethodMultiEnum.RATE.equals(punchInMultiTask.getPunchInMethod())) {
  476. // 没有设置时默认设置一个较大的数,避免出现打卡完成率为0的情况
  477. BigDecimal doneRate = Optional.ofNullable(punchInMultiTask.getPunchInDoneRate()).orElse(DEFAULT_PUNCH_IN_DONE_RATE);
  478. BigDecimal punchInDoneRate = BigDecimal.valueOf(punchInDoneCount).divide(BigDecimal.valueOf(multiTaskNum), 2, RoundingMode.HALF_UP);
  479. return punchInDoneRate.compareTo(doneRate) > -1 ? PunchInStatusEnum.DONE : PunchInStatusEnum.UNDONE;
  480. }
  481. return PunchInStatusEnum.UNDONE;
  482. }
  483. @Override
  484. public int calculatePointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExts, PunchInTaskHistory punchInTaskHistory, PunchInStatsWeek punchInStatsWeek, PunchInStatsMonth punchInStatsMonth, PunchInStatus punchInStatus) {
  485. // 未完成打卡,积分为0
  486. if (PunchInStatusEnum.UNDONE.equals(punchInTaskHistory.getPunchInStatus())) {
  487. return 0;
  488. }
  489. // 单次打卡中使用的拓展信息
  490. List<PunchInTaskExt> punchInExtList = punchInTaskExts.stream().filter(v -> PunchInDimensionEnum.ONE_DAY.equals(v.getDimension())).collect(Collectors.toList());
  491. // 打卡任务使用的拓展信息
  492. List<PunchInTaskExt> punchInTaskExtList = punchInTaskExts.stream().filter(v -> PunchInDimensionEnum.MULTI_DAY.equals(v.getDimension())).collect(Collectors.toList());
  493. // 单个任务积分=基本积分+(可选)额外积分+(可选)连续完成额外积分+法定节假日(含周末)双倍奖励+全勤双倍奖励
  494. // 基本积分
  495. int basicPoints = Optional.ofNullable(punchInTask.getPoints()).orElse(0);
  496. // 额外积分
  497. basicPoints += calculateExtraPointsInTask(punchInTask, punchInExtList, punchInTaskHistory, calendarManager.judgeHoliday(punchInTaskHistory.getPunchInDate()));
  498. // 启用了全勤奖励
  499. int fullAttendancePoints = 0;
  500. if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getFullAttendanceStatus())) {
  501. LocalDate punchInDate = LocalDate.parse(punchInTaskHistory.getPunchInDate());
  502. // 结算周期:周,并且结算日是周末,则双倍奖励;
  503. if (FullAttendancePeriodEnum.WEEK.equals(punchInTask.getFullAttendancePeriod()) && punchInDate.getDayOfWeek().getValue() == 7) {
  504. int undoneCount = punchInStatsWeek.getPunchInTotalCount() - punchInStatsWeek.getPunchInDoneCount();
  505. if (undoneCount <= punchInTask.getFullAttendanceFaultToleranceCnt()) {
  506. fullAttendancePoints = basicPoints;
  507. }
  508. }
  509. // 结算周期:月,结算日是当月最后一天,则双倍奖励;
  510. if (FullAttendancePeriodEnum.MONTH.equals(punchInTask.getFullAttendancePeriod()) && punchInDate.lengthOfMonth() == punchInDate.getDayOfMonth()) {
  511. int undoneCount = punchInStatsMonth.getPunchInTotalCount() - punchInStatsMonth.getPunchInDoneCount();
  512. if (undoneCount <= punchInTask.getFullAttendanceFaultToleranceCnt()) {
  513. fullAttendancePoints = basicPoints;
  514. }
  515. }
  516. }
  517. // 启用了法定节假日(含周末)双倍奖励
  518. int holidayPoints = 0;
  519. if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getHolidayStatus()) && calendarManager.judgeHoliday(punchInTaskHistory.getPunchInDate())) {
  520. holidayPoints = basicPoints;
  521. }
  522. // 连续完成额外积分
  523. int taskPoints = 0;
  524. if (CommonEnableStatusEnum.ENABLED.equals(punchInTask.getTaskPointsStatus()) && ConsecutiveStatusEnum.CONSECUTIVE.equals(punchInStatus.getConsecutiveStatus())) {
  525. // punchInTaskExtList 根据InitialValue倒序排列
  526. punchInTaskExtList.sort(Comparator.comparing(PunchInTaskExt::getInitialValue).reversed());
  527. // 第二轮标志
  528. boolean secondRound = false;
  529. // 上一轮的值
  530. int prevInitialValue = 0;
  531. for (PunchInTaskExt punchInTaskExt : punchInTaskExtList) {
  532. // 比较结果
  533. int compareValue = punchInStatus.getConsecutiveDay().compareTo(punchInTaskExt.getInitialValue());
  534. // 如果连续日期小于initialValue,则跳过
  535. if (compareValue == -1) {
  536. continue;
  537. }
  538. // 额外奖励数
  539. int extraCount = 0;
  540. // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
  541. if (secondRound) {
  542. extraCount = prevInitialValue - punchInTaskExt.getInitialValue();
  543. } else if (compareValue >= 0) {
  544. extraCount = punchInStatus.getConsecutiveDay() - punchInTaskExt.getInitialValue() + 1;
  545. secondRound = true;
  546. }
  547. taskPoints += punchInTaskExt.getExtraPoints() * extraCount;
  548. prevInitialValue = punchInTaskExt.getInitialValue();
  549. }
  550. }
  551. return basicPoints + fullAttendancePoints + holidayPoints + taskPoints;
  552. }
  553. /**
  554. * 计算额外积分
  555. * @param punchInTask
  556. * @param punchInTaskExtList
  557. * @return
  558. */
  559. private int calculateExtraPointsInTask(PunchInTask punchInTask, List<PunchInTaskExt> punchInTaskExtList, PunchInTaskHistory punchInTaskHistory, boolean holidayFlag) {
  560. // 单次打卡或者没有启用额外积分计算则跳过
  561. if (PunchInExtraMethodEnum.NONE.equals(punchInTask.getExtraMethod())
  562. || PunchInMethodEnum.SINGLE.equals(punchInTask.getPunchInMethod())) {
  563. return 0;
  564. }
  565. // 计算超出部分,
  566. int extraCountTrack = 0;
  567. // 计数打卡
  568. if (PunchInMethodEnum.COUNT.equals(punchInTask.getPunchInMethod())) {
  569. extraCountTrack = punchInTaskHistory.getCountTrack() - (holidayFlag ? punchInTask.getHolidayCountTrack() : punchInTask.getCountTrack());
  570. }
  571. // 计时打卡
  572. if (PunchInMethodEnum.TIMING.equals(punchInTask.getPunchInMethod())) {
  573. // 超出的时间按照时间间隔转换成次数
  574. long timeTrack = punchInTaskHistory.getTimeTrack().until((holidayFlag ? punchInTask.getHolidayTimeTrack() : punchInTask.getTimeTrack()), ChronoUnit.MINUTES);
  575. extraCountTrack = BigDecimal.valueOf(timeTrack).divide(BigDecimal.valueOf(punchInTask.getExtraTimeStep()), 0, RoundingMode.FLOOR).intValue();
  576. }
  577. // 如果等于0则没有超出部分,不用计算
  578. if (extraCountTrack == 0) {
  579. return 0;
  580. }
  581. // 固定计算
  582. if (PunchInExtraMethodEnum.FIXED.equals(punchInTask.getExtraMethod())) {
  583. Integer extraPoints = Optional.ofNullable(punchInTask.getExtraPoints()).orElse(0);
  584. return extraCountTrack * extraPoints;
  585. }
  586. // 区间计算
  587. if (PunchInExtraMethodEnum.INTERVAL.equals(punchInTask.getExtraMethod()) && !CollectionUtils.isEmpty(punchInTaskExtList)) {
  588. int basicPoints = 0;
  589. int prevInitialValue = 0;
  590. for (PunchInTaskExt punchInTaskExt : punchInTaskExtList) {
  591. Integer initialValue = Optional.ofNullable(punchInTaskExt.getInitialValue()).orElse(0);
  592. // 第一轮:initialValue - extraCountTrack的值v1
  593. // 如果v1≤0则超出initialValue范围,使用initialValue计算;
  594. // 如果v1>0则在initialValue范围内 使用extraCountTrack
  595. // 后续轮:本轮initialValue - extraCountTrack的值v1、
  596. // 如果v1≤0则超出本轮initialValue范围,使用上一轮initialValue-本轮initialValue的值v2计算;
  597. // 如果v1>0则在本轮initialValue范围内 使用extraCountTrack-上一轮initialValue的值v3计算
  598. // 下面的代码不适合第一轮的计算
  599. if (initialValue - extraCountTrack <= 0) {
  600. basicPoints = punchInTaskExt.getExtraPoints() * Math.abs(prevInitialValue - initialValue);
  601. } else {
  602. basicPoints = punchInTaskExt.getExtraPoints() * (extraCountTrack - prevInitialValue);
  603. }
  604. prevInitialValue = initialValue;
  605. }
  606. return basicPoints;
  607. }
  608. return 0;
  609. }
  610. @Override
  611. public int calculatePointsInMultiTask(PunchInMultiTask punchInMultiTask, List<PunchInMultiTaskExt> punchInMultiTaskExts, PunchInMultiTaskHistory punchInMultiTaskHistory, PunchInStatus punchInStatus) {
  612. // 未完成打卡或没有启用多任务积分计算,积分为0
  613. if (PunchInStatusEnum.UNDONE.equals(punchInMultiTaskHistory.getPunchInStatus())
  614. || CommonEnableStatusEnum.ENABLED.equals(punchInMultiTask.getTaskPointsStatus())) {
  615. return 0;
  616. }
  617. // 单次打卡中使用的拓展信息
  618. List<PunchInMultiTaskExt> punchInExtList = punchInMultiTaskExts.stream().filter(v -> PunchInDimensionEnum.ONE_DAY.equals(v.getDimension())).collect(Collectors.toList());
  619. // 打卡任务使用的拓展信息
  620. List<PunchInMultiTaskExt> punchInTaskExtList = punchInMultiTaskExts.stream().filter(v -> PunchInDimensionEnum.MULTI_DAY.equals(v.getDimension())).collect(Collectors.toList());
  621. // 多个任务积分=多任务基本积分+多任务额外积分+连续完成额外积分
  622. // 基本积分
  623. int basicPoints = punchInMultiTask.getPoints();
  624. // 额外积分
  625. int extraPoints = 0;
  626. // 额外次数
  627. int extractCount = punchInMultiTaskHistory.getPunchInDoneCount() - punchInMultiTask.getPunchInDoneCount();
  628. // 固定计算
  629. if (PunchInExtraMethodEnum.FIXED.equals(punchInMultiTask.getExtraMethod())) {
  630. extraPoints = extractCount * punchInMultiTask.getExtraPoints();
  631. }
  632. // 区间计算
  633. if (PunchInExtraMethodEnum.INTERVAL.equals(punchInMultiTask.getExtraMethod()) && !CollectionUtils.isEmpty(punchInMultiTaskExts)) {
  634. // punchInTaskExtList 根据InitialValue倒序排列
  635. punchInExtList.sort(Comparator.comparing(PunchInMultiTaskExt::getInitialValue).reversed());
  636. // 第二轮标志
  637. boolean secondRound = false;
  638. // 上一轮的值
  639. int prevInitialValue = 0;
  640. for (PunchInMultiTaskExt punchInTaskExt : punchInTaskExtList) {
  641. // 比较结果
  642. int compareValue = Integer.compare(extractCount, punchInTaskExt.getInitialValue());
  643. // 如果连续日期小于initialValue,则跳过
  644. if (compareValue == -1) {
  645. continue;
  646. }
  647. // 额外奖励数
  648. int tempExtraCount = 0;
  649. // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
  650. if (secondRound) {
  651. tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue();
  652. } else if (compareValue >= 0) {
  653. tempExtraCount = extractCount - punchInTaskExt.getInitialValue() + 1;
  654. secondRound = true;
  655. }
  656. extraPoints += punchInTaskExt.getExtraPoints() * tempExtraCount;
  657. prevInitialValue = punchInTaskExt.getInitialValue();
  658. }
  659. }
  660. // 连续完成额外积分
  661. int taskPoints = 0;
  662. if (CommonEnableStatusEnum.ENABLED.equals(punchInMultiTask.getTaskPointsStatus()) && ConsecutiveStatusEnum.CONSECUTIVE.equals(punchInStatus.getConsecutiveStatus())) {
  663. // punchInTaskExtList 根据InitialValue倒序排列
  664. punchInTaskExtList.sort(Comparator.comparing(PunchInMultiTaskExt::getInitialValue).reversed());
  665. // 第二轮标志
  666. boolean secondRound = false;
  667. // 上一轮的值
  668. int prevInitialValue = 0;
  669. for (PunchInMultiTaskExt punchInTaskExt : punchInTaskExtList) {
  670. // 比较结果
  671. int compareValue = punchInStatus.getConsecutiveDay().compareTo(punchInTaskExt.getInitialValue());
  672. // 如果连续日期小于initialValue,则跳过
  673. if (compareValue == -1) {
  674. continue;
  675. }
  676. // 额外奖励数
  677. int tempExtraCount = 0;
  678. // 如果连续日期大于等于initialValue,则进行第一次计算,并把第二轮标志位设置为true,第二轮/后续轮只需要用上一轮的值进行计算
  679. if (secondRound) {
  680. tempExtraCount = prevInitialValue - punchInTaskExt.getInitialValue();
  681. } else if (compareValue >= 0) {
  682. tempExtraCount = punchInStatus.getConsecutiveDay() - punchInTaskExt.getInitialValue() + 1;
  683. secondRound = true;
  684. }
  685. taskPoints += punchInTaskExt.getExtraPoints() * tempExtraCount;
  686. prevInitialValue = punchInTaskExt.getInitialValue();
  687. }
  688. }
  689. return basicPoints + extraPoints + taskPoints;
  690. }
  691. }