| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482 |
- package com.punchsettle.server.service.manager.impl;
- import java.sql.Timestamp;
- import java.text.SimpleDateFormat;
- import java.time.LocalDate;
- import java.time.LocalTime;
- import java.util.ArrayList;
- 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 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.PunchInRecordSettlementRela;
- import com.punchsettle.server.atomic.entity.PunchInSettlement;
- import com.punchsettle.server.atomic.entity.SettlementTask;
- import com.punchsettle.server.atomic.entity.User;
- import com.punchsettle.server.atomic.service.IPunchInRecordService;
- import com.punchsettle.server.atomic.service.IPunchInRecordSettlementRelaService;
- import com.punchsettle.server.atomic.service.IPunchInService;
- import com.punchsettle.server.atomic.service.IPunchInSettlementService;
- import com.punchsettle.server.atomic.service.ISettlementTaskService;
- import com.punchsettle.server.atomic.service.IUserService;
- import com.punchsettle.server.common.exception.BusinessException;
- import com.punchsettle.server.common.utils.Assert;
- import com.punchsettle.server.constant.PunchInCategoryEnum;
- import com.punchsettle.server.constant.PunchInRuleEnum;
- import com.punchsettle.server.constant.PunchInSettleTypeEnum;
- import com.punchsettle.server.constant.PunchInStatusEnum;
- import com.punchsettle.server.pojo.punchin.PunchInQuery;
- import com.punchsettle.server.pojo.punchin.PunchInRecordQuery;
- import com.punchsettle.server.pojo.settle.SettleInfoDto;
- import com.punchsettle.server.pojo.settle.SettleQuery;
- import com.punchsettle.server.pojo.settle.SettleRequest;
- import com.punchsettle.server.pojo.settle.SettleResultDto;
- import com.punchsettle.server.pojo.settle.SettleVO;
- import com.punchsettle.server.service.manager.ISettleManager;
- import com.punchsettle.server.utiis.DateUtils;
- import com.punchsettle.server.utiis.SpringUtils;
- import lombok.extern.slf4j.Slf4j;
- /**
- * @author tyuio
- * @version 1.0.0
- * @description 结算服务类
- * @date 2024/12/12 22:29
- */
- @Slf4j
- @Service
- public class SettleManagerImpl implements ISettleManager {
- @Autowired
- private IPunchInService punchInService;
- @Autowired
- private IPunchInRecordService punchInRecordService;
- @Autowired
- private IUserService userService;
- @Autowired
- private IPunchInSettlementService punchInSettlementService;
- @Autowired
- private ISettlementTaskService settlementTaskService;
- @Autowired
- private IPunchInRecordSettlementRelaService punchInRecordSettlementRelaService;
- @Override
- @Transactional(rollbackFor = Exception.class)
- public void settleHandler(PunchInSettleTypeEnum settleType, LocalDate settleDate, List<Long> userIds, List<Long> punchInIds) {
- if (Objects.isNull(settleType)) {
- log.info("结算任务异常,原因:没有指定结算类型");
- return;
- }
- if (Objects.isNull(settleDate)) {
- log.info("结算任务异常,原因:没有指定结算日期");
- return;
- }
- if (settleDate.isAfter(LocalDate.now())) {
- BusinessException.throwFail(String.format("结算任务异常,原因:结算日期{} 不能大于等于今天", settleDate));
- }
- log.info("结算任务开始,结算类型:{}, 结算日期:{}", settleType.getName(), settleDate);
- Timestamp settleStartTime = new Timestamp(System.currentTimeMillis());
- SettleInfoDto settleInfo = new SettleInfoDto(settleType, settleDate);
- // 读取用户数据,如果userIds为空则结算所有的用户
- userIds = Optional.ofNullable(userIds).orElseGet(() -> {
- PunchInRecordQuery recordQuery = new PunchInRecordQuery();
- recordQuery.setStartDate(settleInfo.getSettleDateStr());
- recordQuery.setEndDate(settleInfo.getSettleDateStr());
- List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(recordQuery);
- return punchInRecords.stream().map(PunchInRecord::getCreatedBy).collect(Collectors.toList());
- });
- List<User> users = userService.listByIds(userIds);
- if (CollectionUtils.isEmpty(users)) {
- log.info("结算任务结束,原因:没有找到待结算的用户信息");
- return;
- }
- // 读取用户的打卡任务,如果punchIds为空则结算用户所有的打卡任务
- PunchInQuery punchInQuery = new PunchInQuery();
- punchInQuery.setUserIds(userIds);
- if (!CollectionUtils.isEmpty(punchInIds)) {
- punchInQuery.setPunchInIds(punchInIds);
- }
- List<PunchIn> punchIns = punchInService.listByCondition(punchInQuery);
- if (CollectionUtils.isEmpty(punchIns)) {
- log.info("结算任务结束,原因:没有找到打卡任务");
- return;
- }
- // 获取最新的打卡任务ID
- punchInIds = punchIns.stream().map(PunchIn::getId).collect(Collectors.toList());
- // 获取结算日的打卡记录
- PunchInRecordQuery recordQuery = new PunchInRecordQuery();
- recordQuery.setStartDate(settleInfo.getSettleDateStr());
- recordQuery.setEndDate(settleInfo.getSettleDateStr());
- recordQuery.setPunchInIds(punchInIds);
- List<PunchInRecord> punchInRecords = punchInRecordService.listByCondition(recordQuery);
- if (CollectionUtils.isEmpty(punchInRecords) && !PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType())) {
- log.info("结算任务结束,原因:没有打卡记录");
- return;
- }
- // 获取一周的打卡记录
- Map<Long, List<PunchInRecord>> weeklyPunchInRecords = Map.of();
- if (settleInfo.getSundayFlag() || PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType())) {
- PunchInRecordQuery weeklyRecordQuery = new PunchInRecordQuery();
- weeklyRecordQuery.setPunchInIds(punchInIds);
- weeklyRecordQuery.setStartDate(DateUtils.getLastWeekMonday(settleInfo.getSettleDate()).toString());
- weeklyRecordQuery.setEndDate(DateUtils.getLastWeekSunday(settleInfo.getSettleDate()).toString());
- List<PunchInRecord> punchInRecordForWeeks = punchInRecordService.listByCondition(weeklyRecordQuery);
- if (!CollectionUtils.isEmpty(punchInRecordForWeeks)) {
- weeklyPunchInRecords = punchInRecordForWeeks.stream().collect(Collectors.groupingBy(PunchInRecord::getPunchInId));
- }
- }
- // 用户-打卡任务分组
- Map<Long, List<PunchIn>> userPunchInMap = punchIns.stream().collect(Collectors.groupingBy(PunchIn::getCreatedBy));
- // 打卡任务-打卡记录 map
- Map<Long, PunchInRecord> punchInRecordMap = punchInRecords.stream().collect(Collectors.toMap(PunchInRecord::getPunchInId, Function.identity(), (key1, key2) -> key1));
- // 先创建结算任务执行记录
- SettlementTask settlementTask = new SettlementTask();
- settlementTask.setSettleDate(settleDate.toString());
- settlementTask.setStartTime(settleStartTime);
- settlementTask.setProcessedNum(users.size());
- settlementTaskService.insert(settlementTask);
- // 结算
- List<SettleResultDto> settleResultDtoList = new ArrayList<>();
- for (User user : users) {
- SettleResultDto settleResult = settle(settleInfo, user, userPunchInMap.get(user.getId()), punchInRecordMap, weeklyPunchInRecords);
- settleResultDtoList.add(settleResult);
- }
- // 更新数据
- // 待更新的用户
- List<User> updateUsers = new ArrayList<>();
- // 待更新的打卡记录
- List<PunchInRecord> updatePunchInRecords = new ArrayList<>();
- // 待新增的打卡记录
- List<PunchInRecord> addPunchInRecords = new ArrayList<>();
- // 待新增的打卡结算信息
- List<PunchInSettlement> addPunchInSettlements = new ArrayList<>();
- for (SettleResultDto settleResult : settleResultDtoList) {
- updateUsers.add(settleResult.getUpdateUser());
- addPunchInRecords.addAll(settleResult.getAddPunchInRecords());
- updatePunchInRecords.addAll(settleResult.getUpdatePunchInRecords());
- addPunchInSettlements.add(settleResult.getAddPunchInSettlement());
- }
- // 更新用户奖励信息
- if (!CollectionUtils.isEmpty(updateUsers)) {
- userService.batchUpdateUser(updateUsers);
- }
- // 新增打卡记录
- if (!CollectionUtils.isEmpty(addPunchInRecords)) {
- punchInRecordService.batchInsert(addPunchInRecords);
- }
- // 更新已有的打卡记录
- if (!CollectionUtils.isEmpty(updatePunchInRecords)) {
- punchInRecordService.batchUpdate(updatePunchInRecords);
- }
- // 新增结算信息
- if (!CollectionUtils.isEmpty(addPunchInSettlements)) {
- // 补充本次的计算任务ID
- addPunchInSettlements.stream().forEach(v -> v.setSettlementTaskId(settlementTask.getId()));
- punchInSettlementService.batchInsert(addPunchInSettlements);
- }
- // 新增关联信息
- if (!CollectionUtils.isEmpty(settleResultDtoList)) {
- List<PunchInRecordSettlementRela> relaList = buildPunchInRecordSettlementRelaList(settleResultDtoList);
- if (!CollectionUtils.isEmpty(relaList)) {
- punchInRecordSettlementRelaService.batchInsert(relaList);
- }
- }
- // 构造并新增结算任务信息
- settlementTask.setProcessedSettleNum(addPunchInSettlements.size());
- settlementTask.setProcessedUnsettleNum(settlementTask.getProcessedNum() - settlementTask.getProcessedSettleNum());
- settlementTask.setEndTime(new Timestamp(System.currentTimeMillis()));
- settlementTaskService.update(settlementTask);
- log.info("结算任务结束");
- }
- /**
- * 结算
- * @param settleInfo 结算基本信息
- * @param user 待结算的用户
- * @param punchIns 打卡任务
- * @param punchInRecordMap 打卡任务-打卡记录 map
- * @param weeklyPunchInRecordMap 打卡任务-一周打卡记录 map
- * @return
- */
- private SettleResultDto settle(SettleInfoDto settleInfo, User user, List<PunchIn> punchIns, Map<Long, PunchInRecord> punchInRecordMap, Map<Long, List<PunchInRecord>> weeklyPunchInRecordMap) {
- // 结算奖励数
- int settleRewardNum = 0;
- // 待更新的打卡记录
- List<PunchInRecord> updatePunchInRecords = new ArrayList<>();
- // 待新增的打卡记录
- List<PunchInRecord> addPunchInRecords = new ArrayList<>();
- // 结算
- for (PunchIn punchIn : punchIns) {
- // 获取打卡记录
- PunchInRecord punchInRecord = punchInRecordMap.get(punchIn.getId());
- // 不是补打卡且不存在打卡记录直接跳过,无须结算和更新记录状态
- if (!PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && Objects.isNull(punchInRecord)) {
- continue;
- }
- // 判断是否满足打卡规则
- PunchInStatusEnum punchInStatus = judgePunchInStatus(punchIn, punchInRecord);
- // 不是补卡或打卡任务未完成,则跳过
- if (!PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && PunchInStatusEnum.UN_FINISH.equals(punchInStatus)) {
- PunchInRecord updatePunchInRecord = buildPunchInRecordForSettle(punchIn);
- updatePunchInRecord.setId(punchInRecord.getId());
- updatePunchInRecord.setPunchInStatus(punchInStatus);
- updatePunchInRecords.add(updatePunchInRecord);
- continue;
- }
- // 补打卡,完全没有打卡记录则需要补充打卡记录
- if (PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && Objects.isNull(punchInRecord)) {
- punchInRecord = buildPunchInRecordForSettle(punchIn);
- punchInRecord.setPunchInId(punchIn.getId());
- punchInRecord.setPunchInDate(settleInfo.getSettleDateStr());
- punchInRecord.setPunchInStatus(PunchInStatusEnum.REMAKE_FINISH);
- addPunchInRecords.add(punchInRecord);
- }
- // 补打卡,已有打卡记录但是不满足打卡规则,则需要对打卡记录做准备
- if (PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType()) && PunchInStatusEnum.UN_FINISH.equals(punchInStatus)) {
- fillTrack(punchIn, punchInRecord);
- }
- // 不是补卡,且打卡状态为完成,需要更新打卡状态为完成
- if (Objects.isNull(punchInRecord.getPunchInStatus()) || PunchInStatusEnum.DOING.equals(punchInRecord.getPunchInStatus())) {
- PunchInRecord updatePunchInRecord = buildPunchInRecordForSettle(punchIn);
- updatePunchInRecord.setId(punchInRecord.getId());
- updatePunchInRecord.setPunchInStatus(punchInStatus);
- updatePunchInRecords.add(updatePunchInRecord);
- }
- // 周末双倍奖励,否则计算普通奖励
- settleRewardNum += settleInfo.getWeekendFlag() && punchIn.getWeekendDoubleFlag() ? punchIn.getRewardNum() * 2 : punchIn.getRewardNum();
- // 计算全勤双倍奖励
- if (judgeFullAttendance(settleInfo, punchIn, weeklyPunchInRecordMap)) {
- settleRewardNum += punchIn.getRewardNum() * 2;
- }
- }
- // 计算结算前后,用户奖励数的变化
- int beforeSettleRewardNum = user.getUnclaimedRewardNum();
- int afterSettleRewardNum = beforeSettleRewardNum + settleRewardNum;
- int totalRewardNum = user.getTotalRewardNum() + settleRewardNum;
- // 构造结算信息
- PunchInSettlement addPunchInSettlement = new PunchInSettlement();
- addPunchInSettlement.setUserId(user.getId());
- addPunchInSettlement.setSettleRewardNum(settleRewardNum);
- addPunchInSettlement.setSettlementTime(new Timestamp(System.currentTimeMillis()));
- addPunchInSettlement.setBeforeSettleRewardNum(beforeSettleRewardNum);
- addPunchInSettlement.setAfterSettleRewardNum(afterSettleRewardNum);
- // 构造用户奖励信息
- User updateUser = new User();
- updateUser.setId(user.getId());
- updateUser.setTotalRewardNum(totalRewardNum);
- updateUser.setUnclaimedRewardNum(afterSettleRewardNum);
- SettleResultDto settleResultDto = new SettleResultDto();
- settleResultDto.setUpdateUser(updateUser);
- settleResultDto.setAddPunchInRecords(addPunchInRecords);
- settleResultDto.setUpdatePunchInRecords(updatePunchInRecords);
- settleResultDto.setAddPunchInSettlement(addPunchInSettlement);
- return settleResultDto;
- }
- @Override
- public PunchInStatusEnum judgePunchInStatus(PunchIn punchIn, PunchInRecord punchInRecord) {
- // 没有打卡记录,直接没完成,包含单次打卡的情况无需额外判断
- if (Objects.isNull(punchInRecord)) {
- return PunchInStatusEnum.UN_FINISH;
- }
- // 单次打卡
- if (PunchInCategoryEnum.SINGLE.equals(punchIn.getCategory())) {
- return PunchInStatusEnum.FINISH;
- }
- // 计数打卡
- if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())) {
- Integer recordCountTrack = Optional.ofNullable(punchInRecord.getCountTrack()).orElse(0);
- if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule()) && recordCountTrack.compareTo(punchIn.getCountTrack()) == -1) {
- return PunchInStatusEnum.UN_FINISH;
- }
- if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule()) && recordCountTrack.compareTo(punchIn.getCountTrack()) == 1) {
- return PunchInStatusEnum.UN_FINISH;
- }
- return PunchInStatusEnum.FINISH;
- }
- // 计时打卡
- if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
- LocalTime recordTimeTrack = Optional.ofNullable(punchInRecord.getTimeTrack()).orElse(LocalTime.parse("00:00:00.000"));
- if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule()) && recordTimeTrack.compareTo(punchIn.getTimeTrack()) == -1) {
- return PunchInStatusEnum.UN_FINISH;
- }
- if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule()) && recordTimeTrack.compareTo(punchIn.getTimeTrack()) == 1) {
- return PunchInStatusEnum.UN_FINISH;
- }
- return PunchInStatusEnum.FINISH;
- }
- return PunchInStatusEnum.UN_FINISH;
- }
- /**
- * 填充打卡记录的记录信息(不用考虑单次打卡的情况)
- * @param punchIn
- * @param punchInRecord
- */
- private void fillTrack(PunchIn punchIn, PunchInRecord punchInRecord) {
- // 计数打卡
- if (PunchInCategoryEnum.COUNT.equals(punchIn.getCategory())) {
- if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule())) {
- punchInRecord.setCountTrack(punchIn.getCountTrack() + 1);
- }
- if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule())){
- punchInRecord.setCountTrack(punchIn.getCountTrack() - 1);
- }
- }
- // 计时打卡
- if (PunchInCategoryEnum.TIME.equals(punchIn.getCategory())) {
- if (PunchInRuleEnum.GREATER_OR_EQUAL.equals(punchIn.getRule())) {
- punchInRecord.setTimeTrack(punchIn.getTimeTrack().plusSeconds(1));
- }
- if (PunchInRuleEnum.LESS_OR_EQUAL.equals(punchIn.getRule())){
- punchInRecord.setTimeTrack(punchIn.getTimeTrack().minusSeconds(1));
- }
- }
- }
- /**
- * 判断是否进行全勤结算
- * @param settleInfo 结算信息
- * @param punchIn 打卡任务
- * @param weeklyPunchInRecordMap 一周打卡记录
- * @return true-
- */
- private boolean judgeFullAttendance(SettleInfoDto settleInfo, PunchIn punchIn, Map<Long, List<PunchInRecord>> weeklyPunchInRecordMap) {
- // 没有启用全勤奖励则跳过
- if (!punchIn.getFullAttendanceFlag()) {
- return false;
- }
- // 不是周日结算或者补打卡则跳过,
- if (!settleInfo.getSundayFlag() && !PunchInSettleTypeEnum.REMAKE.equals(settleInfo.getSettleType())) {
- return false;
- }
- // 获取一周的完成打卡的打卡记录,并且要排除结算日这天的记录
- List<PunchInRecord> weeklyFinishRecord = Optional.ofNullable(weeklyPunchInRecordMap.get(punchIn.getId())).orElse(new ArrayList<>())
- .stream().filter(record -> !settleInfo.getSettleDateStr().equals(record.getPunchInDate()) && (PunchInStatusEnum.FINISH.equals(record.getPunchInStatus()) || PunchInStatusEnum.REMAKE_FINISH.equals(record.getPunchInStatus())))
- .collect(Collectors.toList());
- // 1个是容错允许不打卡或未完成打卡,1个是当天的结算打卡来到这段逻辑就认为已经完成打卡,因此只要有5个完成打卡,则认为全勤
- return weeklyFinishRecord.size() >= 5;
- }
- /**
- * 构建待更新结算数据的打卡记录
- * @return
- */
- private PunchInRecord buildPunchInRecordForSettle(PunchIn punchIn) {
- PunchInRecord updatePunchInRecord = new PunchInRecord();
- updatePunchInRecord.setSettleRewardNum(punchIn.getRewardNum());
- updatePunchInRecord.setSettleCategory(punchIn.getCategory());
- updatePunchInRecord.setSettleRule(punchIn.getRule());
- updatePunchInRecord.setSettleCountTrack(punchIn.getCountTrack());
- updatePunchInRecord.setSettleTimeTrack(punchIn.getTimeTrack());
- return updatePunchInRecord;
- }
- /**
- * 构造打卡记录与结算记录的关联关系
- * @param settleResultDtos
- * @return
- */
- private static List<PunchInRecordSettlementRela> buildPunchInRecordSettlementRelaList(List<SettleResultDto> settleResultDtos) {
- if (CollectionUtils.isEmpty(settleResultDtos)) {
- return List.of();
- }
- List<PunchInRecordSettlementRela> relaList = new ArrayList();
- // 补充关联关系ID(结算记录ID、打卡记录ID)
- for (SettleResultDto settleResultDto : settleResultDtos) {
- for (PunchInRecord punchInRecord : settleResultDto.getAddPunchInRecords()) {
- PunchInRecordSettlementRela rela = new PunchInRecordSettlementRela();
- rela.setRecordId(punchInRecord.getId());
- rela.setSettlementId(settleResultDto.getAddPunchInSettlement().getId());
- relaList.add(rela);
- }
- for (PunchInRecord punchInRecord : settleResultDto.getUpdatePunchInRecords()) {
- PunchInRecordSettlementRela rela = new PunchInRecordSettlementRela();
- rela.setRecordId(punchInRecord.getId());
- rela.setSettlementId(settleResultDto.getAddPunchInSettlement().getId());
- relaList.add(rela);
- }
- }
- return relaList;
- }
- @Override
- public void manualSettle(SettleRequest settleRequest) {
- // TODO 这里考虑加判断条件防止重复手动结算
- Assert.isNullInBusiness(settleRequest, "结算请求不能为空");
- if (!PunchInSettleTypeEnum.OPS.equals(settleRequest.getSettleType())) {
- BusinessException.throwFail("非运维结算,禁止执行");
- }
- SpringUtils.getBean(ISettleManager.class).settleHandler(settleRequest.getSettleType(), LocalDate.parse(settleRequest.getSettleDate()), settleRequest.getUserIds(), settleRequest.getPunchInIds());
- }
- @Override
- public List<SettleVO> querySettle(SettleQuery query) {
- SimpleDateFormat sdf = DateUtils.buildDateTimeFormat();
- List<PunchInSettlement> punchInSettlements = punchInSettlementService.listByCondition(query);
- return punchInSettlements.stream().map(settlement -> {
- SettleVO settleVO = new SettleVO();
- BeanUtils.copyProperties(settlement, settleVO);
- settleVO.setSettlementTime(sdf.format(settlement.getSettlementTime()));
- return settleVO;
- }).toList();
- }
- }
|