解决依赖循环问题/新增调整人员立即生效

This commit is contained in:
aikai 2024-06-07 10:30:55 +08:00
parent e6292c3ae2
commit 965215996a
13 changed files with 336 additions and 251 deletions

View File

@ -72,14 +72,6 @@ public class AttendanceGroupController {
}
@GetMapping("/test")
@PermitAll
public CommonResult test() {
groupService.test();
return success("ok");
}
@GetMapping("/page")
@Operation(summary = "获得考勤组分页")
@PreAuthorize("@ss.hasPermission('attendance:group:query')")

View File

@ -50,7 +50,7 @@ public class AttendanceGroupShiftController {
return success(groupShiftService.createGroupShift(createReqVO));
}
// TODO: 2024/6/5 如果返回的有值 (判断不是空数组) - 那么前端 跳出提示框 (该班次已有考勤组在使用请选择更新方式) 下面两个按钮 立即生效 (推荐)次日生效
// 如果返回的有值 (判断不是空数组) - 那么前端 跳出提示框 (该班次已有考勤组在使用请选择更新方式) 下面两个按钮 立即生效 (推荐)次日生效
@PutMapping("/update")
@Operation(summary = "更新考勤组班次")
@PreAuthorize("@ss.hasPermission('attendance:group-shift:update')")

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.system.controller.admin.groupuser.vo.AttendanceGroupUserCreateOrDelVO;
import cn.iocoder.yudao.module.system.controller.admin.groupuser.vo.AttendanceGroupUserPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.groupuser.vo.AttendanceGroupUserRespVO;
import cn.iocoder.yudao.module.system.controller.admin.groupuser.vo.AttendanceGroupUserSaveReqVO;
@ -39,10 +40,17 @@ public class AttendanceGroupUserController {
@PostMapping("/createOrDel")
@Operation(summary = "创建删除考勤组人员")
@PreAuthorize("@ss.hasPermission('attendance:group-user:create')")
public CommonResult createOrDel(@RequestParam Long attendanceGroupId,
@Valid @RequestBody List<Long> userIds) {
groupUserService.createOrDel(attendanceGroupId, userIds);
return success("ok");
public CommonResult<AttendanceGroupUserCreateOrDelVO> createOrDel(@RequestParam Long attendanceGroupId,
@Valid @RequestBody List<Long> userIds) {
AttendanceGroupUserCreateOrDelVO vo = groupUserService.createOrDel(attendanceGroupId, userIds);
return success(vo);
}
@PostMapping("/effectiveImmediately")
@Operation(summary = "立即生效")
public CommonResult effectiveImmediately(@RequestBody AttendanceGroupUserCreateOrDelVO vo) {
groupUserService.effectiveImmediately(vo);
return success(vo);
}
@PutMapping("/update")

View File

@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.system.controller.admin.groupuser.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
public class AttendanceGroupUserCreateOrDelVO {
@Schema(description = "考勤组id")
private Long attendanceGroupId;
@Schema(description = "删除的用户")
private List<Long> delUserIds;
@Schema(description = "新增的用户")
private List<Long> saveUserIds;
}

View File

@ -1,41 +1,17 @@
package cn.iocoder.yudao.module.system.job.attendance;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.Constants;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.group.AttendanceGroupDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupshiftitem.AttendanceGroupShiftItemDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupuser.AttendanceGroupUserDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord.AttendancePunchRecordDO;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.group.AttendanceGroupMapper;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.groupuser.AttendanceGroupUserMapper;
import cn.iocoder.yudao.module.system.service.attendance.AttendanceService;
import cn.iocoder.yudao.module.system.service.attendance.fixed.AttendanceFixedService;
import cn.iocoder.yudao.module.system.service.attendance.groupshiftitem.AttendanceGroupShiftItemService;
import cn.iocoder.yudao.module.system.service.attendance.punch.dto.AttendanceOnTheDayDTO;
import cn.iocoder.yudao.module.system.service.attendance.punchrecord.AttendancePunchRecordService;
import cn.iocoder.yudao.module.system.service.attendance.scheduling.AttendanceSchedulingService;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.Collections;
@Component
@Slf4j
@ -55,110 +31,17 @@ public class AttendanceStatisticsJob {
* / -- 5. 如果我凌晨1点 明天的数据 会有什么问题吗 好像没什么问题 -
*/
@Resource
private AttendanceService attendanceService;
@Resource
private AttendanceGroupMapper attendanceGroupMapper;
@Resource
private AttendanceGroupUserMapper attendanceGroupUserMapper;
@Resource
private AttendanceFixedService attendanceFixedService;
@Resource
private AttendanceSchedulingService attendanceSchedulingService;
@Resource
private AttendanceGroupShiftItemService attendanceGroupShiftItemService;
@Resource
private AttendancePunchRecordService attendancePunchRecordService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@XxlJob("attendanceStatisticsJob")
@TenantJob // --- 这个注解 会将租户列表拉出来 完了后逐个租户执行 定时任务需要注意
public ReturnT<String> execute() throws Exception {
log.info("开始 考勤预设");
// 获取所有考勤组
List<AttendanceGroupDO> attendanceGroupDOS = attendanceGroupMapper.selectList();
// -- 根据考勤组ids 获取所有人员
List<AttendanceGroupUserDO> attendanceGroupUserDOS = attendanceGroupUserMapper.selectList();
//将attendanceGroupUserDOS对象中的type相同的userId合并成一个数组 type作为key 用户id列表List<Long>作为value
Map<Long, List<Long>> groupUserMap = attendanceGroupUserDOS.stream().collect(Collectors.groupingBy(AttendanceGroupUserDO::getAttendanceGroupId,
Collectors.mapping(AttendanceGroupUserDO::getUserId, Collectors.toList())));
// -- 获取考勤组下考勤规则 - 将将考勤组分组 - 按类型
LocalDateTime tomorrowLocalDateTime = LocalDateTimeUtil.offset(LocalDateTime.now(), 1, ChronoUnit.DAYS);
LocalDateTime theDayAfterTomorrowLocalDateTime = LocalDateTimeUtil.offset(tomorrowLocalDateTime, 1, ChronoUnit.DAYS);
String time = tomorrowLocalDateTime.format(Constants.REPO_DATE_FORMAT);
// 获取到考勤组 - 班次 key/value 格式
Map<Long, Long> map = this.getAttendanceGroupShiftIdGroupByGroup(attendanceGroupDOS, tomorrowLocalDateTime);
Map<Long, AttendanceGroupDO> groupMap = attendanceGroupDOS.stream().collect(Collectors.toMap(AttendanceGroupDO::getId, v -> v));
// -- 获取班次子表列表
if (MapUtil.isNotEmpty(map)) {
List<AttendanceGroupShiftItemDO> attendanceGroupShiftItemDOList = attendanceGroupShiftItemService.getGroupShiftItemListByShiftIds(new ArrayList<>(map.values()));
Map<Long, List<AttendanceGroupShiftItemDO>> itemMaps = attendanceGroupShiftItemDOList.stream().collect(Collectors.groupingBy(AttendanceGroupShiftItemDO::getKqAttendanceGroupShiftId));
List<AttendancePunchRecordDO> attendancePunchRecordDOList = new ArrayList<>();
for (Map.Entry<Long, Long> entry : map.entrySet()) {
String key = Constants.ATTENDANCE + Constants.UNDERLINE + entry.getKey() + Constants.UNDERLINE; // + 时间
AttendanceGroupDO attendanceGroupDO = groupMap.get(entry.getKey());
List<Long> userIds = groupUserMap.get(entry.getKey());
if (CollectionUtil.isEmpty(userIds)) {
continue;
}
List<AttendanceGroupShiftItemDO> attendanceGroupShiftItemDOS = itemMaps.get(entry.getValue());
if (CollectionUtil.isEmpty(attendanceGroupShiftItemDOS)) {
continue;
}
List<AttendanceOnTheDayDTO> attendanceOnTheDayDTOS = attendanceService.buildAttendanceOnTheDay(attendanceGroupShiftItemDOS);
for (Long userId : userIds) {
for (AttendanceOnTheDayDTO attendanceOnTheDayDTO : attendanceOnTheDayDTOS) {
AttendancePunchRecordDO attendancePunchRecordDO = new AttendancePunchRecordDO();
attendancePunchRecordDO.setUserId(userId);
attendancePunchRecordDO.setAttendanceGroupId(entry.getKey());
attendancePunchRecordDO.setAttendanceGroupName(attendanceGroupDO.getGroupName());
attendancePunchRecordDO.setAttendanceGroupShiftId(attendanceOnTheDayDTO.getKqAttendanceGroupShiftId());
attendancePunchRecordDO.setAttendanceGroupShiftName(attendanceOnTheDayDTO.getKqAttendanceGroupShiftName());
attendancePunchRecordDO.setAttendanceGroupShiftItemId(attendanceOnTheDayDTO.getId());
attendancePunchRecordDO.setType(attendanceGroupDO.getType());
attendancePunchRecordDO.setPunchType(attendanceGroupDO.getPunchType());
attendancePunchRecordDO.setWorkType(attendanceOnTheDayDTO.getType());
attendancePunchRecordDO.setLevel(attendanceOnTheDayDTO.getLevel());
attendancePunchRecordDO.setStatus(AttendanceOnTheDayDTO.PUNCH_STATUS_UN_PUNCH);
attendancePunchRecordDO.setFieldServiceFlag(Constants.FALSE);
attendancePunchRecordDO.setNextDayFlag(Constants.TRUE);
attendancePunchRecordDO.setDayTime(time);
LocalDateTime shouldPunchTime = LocalDateTime.ofInstant(DateUtils.buildHHmmTime(attendanceOnTheDayDTO.getTime(),
(attendanceOnTheDayDTO.getNextDayFlag() == 0 ? tomorrowLocalDateTime : theDayAfterTomorrowLocalDateTime)).toInstant(), ZoneId.systemDefault());
attendancePunchRecordDO.setShouldPunchTime(shouldPunchTime);
attendancePunchRecordDO.setLatestPunchTime(shouldPunchTime.plusMinutes(attendanceOnTheDayDTO.getAfterPunchTime()));
attendancePunchRecordDOList.add(attendancePunchRecordDO);
}
stringRedisTemplate.opsForHash().put(key + time, userId, JSONUtil.toJsonStr(attendanceOnTheDayDTOS));
}
//设置缓存 2天
stringRedisTemplate.expire(key + time, 2, TimeUnit.DAYS);
}
// -- 批量
attendancePunchRecordService.saveBatch(attendancePunchRecordDOList);
}
attendancePunchRecordService.defaultPersistence(Collections.emptyList(), tomorrowLocalDateTime);
log.info("结束 考勤预设");
// 返回执行成功
return ReturnT.SUCCESS;
}
private Map<Long, Long> getAttendanceGroupShiftIdGroupByGroup(List<AttendanceGroupDO> attendanceGroupDOS, LocalDateTime localDateTime) {
Map<Long, Long> map = new HashMap<>();
List<AttendanceGroupDO> fixedList = attendanceGroupDOS.stream().filter(a -> a.getType().equals(1)).collect(Collectors.toList());
List<AttendanceGroupDO> schedulingList = attendanceGroupDOS.stream().filter(a -> a.getType().equals(2)).collect(Collectors.toList());
if (!fixedList.isEmpty()) {
Map<Long, Long> attendanceFixedMap = attendanceFixedService.getGroupToShiftIdMap(fixedList, localDateTime);
if (ObjectUtil.isNotEmpty(attendanceFixedMap)) {
map.putAll(attendanceFixedMap);
}
}
if (!schedulingList.isEmpty()) {
Map<Long, Long> attendanceSchedulingMap = attendanceSchedulingService.getGroupToShiftIdMap(schedulingList, localDateTime);
if (ObjectUtil.isNotEmpty(attendanceSchedulingMap)) {
map.putAll(attendanceSchedulingMap);
}
}
return map;
}
}

View File

@ -61,8 +61,6 @@ public interface AttendanceGroupService {
*/
AttendanceGroupDO getByUserId(Long userId);
void test();
/**
* 获取所有
*

View File

@ -126,70 +126,6 @@ public class AttendanceGroupServiceImpl implements AttendanceGroupService {
return null;
}
@Override
public void test() {
// 获取所有考勤组
List<AttendanceGroupDO> attendanceGroupDOS = attendanceGroupMapper.selectList();
// -- 根据考勤组ids 获取所有人员
List<AttendanceGroupUserDO> attendanceGroupUserDOS = attendanceGroupUserMapper.selectList();
//将attendanceGroupUserDOS对象中的type相同的userId合并成一个数组 type作为key 用户id列表List<Long>作为value
Map<Long, List<Long>> groupUserMap = attendanceGroupUserDOS.stream().collect(Collectors.groupingBy(AttendanceGroupUserDO::getAttendanceGroupId,
Collectors.mapping(AttendanceGroupUserDO::getUserId, Collectors.toList())));
// -- 获取考勤组下考勤规则 - 将将考勤组分组 - 按类型
LocalDateTime tomorrowLocalDateTime = LocalDateTime.now();
LocalDateTime theDayAfterTomorrowLocalDateTime = LocalDateTimeUtil.offset(tomorrowLocalDateTime, 1, ChronoUnit.DAYS);
String time = tomorrowLocalDateTime.format(Constants.REPO_DATE_FORMAT);
// 获取到考勤组 - 班次 key/value 格式
Map<Long, Long> map = this.getAttendanceGroupShiftIdGroupByGroup(attendanceGroupDOS, tomorrowLocalDateTime);
Map<Long, AttendanceGroupDO> groupMap = attendanceGroupDOS.stream().collect(Collectors.toMap(AttendanceGroupDO::getId, v -> v));
// -- 获取班次子表列表
if (MapUtil.isNotEmpty(map)) {
List<AttendanceGroupShiftItemDO> attendanceGroupShiftItemDOList = attendanceGroupShiftItemService.getGroupShiftItemListByShiftIds(new ArrayList<>(map.values()));
Map<Long, List<AttendanceGroupShiftItemDO>> itemMaps = attendanceGroupShiftItemDOList.stream().collect(Collectors.groupingBy(AttendanceGroupShiftItemDO::getKqAttendanceGroupShiftId));
List<AttendancePunchRecordDO> attendancePunchRecordDOList = new ArrayList<>();
for (Map.Entry<Long, Long> entry : map.entrySet()) {
AttendanceGroupDO attendanceGroupDO = groupMap.get(entry.getKey());
List<Long> userIds = groupUserMap.get(entry.getKey());
if (CollectionUtil.isEmpty(userIds)) {
continue;
}
List<AttendanceGroupShiftItemDO> attendanceGroupShiftItemDOS = itemMaps.get(entry.getValue());
if (CollectionUtil.isEmpty(attendanceGroupShiftItemDOS)) {
continue;
}
List<AttendanceOnTheDayDTO> attendanceOnTheDayDTOS = attendanceService.buildAttendanceOnTheDay(attendanceGroupShiftItemDOS);
for (Long userId : userIds) {
for (AttendanceOnTheDayDTO attendanceOnTheDayDTO : attendanceOnTheDayDTOS) {
AttendancePunchRecordDO attendancePunchRecordDO = new AttendancePunchRecordDO();
attendancePunchRecordDO.setUserId(userId);
attendancePunchRecordDO.setAttendanceGroupId(entry.getKey());
attendancePunchRecordDO.setAttendanceGroupName(attendanceGroupDO.getGroupName());
attendancePunchRecordDO.setAttendanceGroupShiftId(attendanceOnTheDayDTO.getKqAttendanceGroupShiftId());
attendancePunchRecordDO.setAttendanceGroupShiftName(attendanceOnTheDayDTO.getKqAttendanceGroupShiftName());
attendancePunchRecordDO.setAttendanceGroupShiftItemId(attendanceOnTheDayDTO.getId());
attendancePunchRecordDO.setType(attendanceGroupDO.getType());
attendancePunchRecordDO.setPunchType(attendanceGroupDO.getPunchType());
attendancePunchRecordDO.setWorkType(attendanceOnTheDayDTO.getType());
attendancePunchRecordDO.setLevel(attendanceOnTheDayDTO.getLevel());
attendancePunchRecordDO.setStatus(AttendanceOnTheDayDTO.PUNCH_STATUS_UN_PUNCH);
attendancePunchRecordDO.setFieldServiceFlag(Constants.FALSE);
attendancePunchRecordDO.setNextDayFlag(Constants.TRUE);
attendancePunchRecordDO.setDayTime(time);
LocalDateTime shouldPunchTime = LocalDateTime.ofInstant(DateUtils.buildHHmmTime(attendanceOnTheDayDTO.getTime(),
(attendanceOnTheDayDTO.getNextDayFlag() == 0 ? tomorrowLocalDateTime : theDayAfterTomorrowLocalDateTime)).toInstant(), ZoneId.systemDefault());
attendancePunchRecordDO.setShouldPunchTime(shouldPunchTime);
attendancePunchRecordDO.setLatestPunchTime(shouldPunchTime.plusMinutes(attendanceOnTheDayDTO.getAfterPunchTime()));
attendancePunchRecordDOList.add(attendancePunchRecordDO);
}
}
}
// -- 批量
attendancePunchRecordService.saveBatch(attendancePunchRecordDOList);
}
}
@Override
public List<AttendanceGroupDO> getAll() {
return attendanceGroupMapper.selectList();
@ -205,24 +141,4 @@ public class AttendanceGroupServiceImpl implements AttendanceGroupService {
return ids;
}
private Map<Long, Long> getAttendanceGroupShiftIdGroupByGroup(List<AttendanceGroupDO> attendanceGroupDOS, LocalDateTime localDateTime) {
Map<Long, Long> map = new HashMap<>();
List<AttendanceGroupDO> fixedList = attendanceGroupDOS.stream().filter(a -> a.getType().equals(1)).collect(Collectors.toList());
List<AttendanceGroupDO> schedulingList = attendanceGroupDOS.stream().filter(a -> a.getType().equals(2)).collect(Collectors.toList());
if (!fixedList.isEmpty()) {
Map<Long, Long> attendanceFixedMap = attendanceFixedService.getGroupToShiftIdMap(fixedList, localDateTime);
if (ObjectUtil.isNotEmpty(attendanceFixedMap)) {
map.putAll(attendanceFixedMap);
}
}
if (!schedulingList.isEmpty()) {
Map<Long, Long> attendanceSchedulingMap = attendanceSchedulingService.getGroupToShiftIdMap(schedulingList, localDateTime);
if (ObjectUtil.isNotEmpty(attendanceSchedulingMap)) {
map.putAll(attendanceSchedulingMap);
}
}
return map;
}
}

View File

@ -69,6 +69,7 @@ public interface AttendanceGroupShiftService {
List<AttendanceGroupShiftVO> listByShiftIds(List<Long> listByShiftIds);
/**
* 立即生效
*
* @param groupIds
*/

View File

@ -13,12 +13,15 @@ import cn.iocoder.yudao.module.system.controller.admin.groupshift.vo.AttendanceG
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.fixed.AttendanceFixedDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupshift.AttendanceGroupShiftDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupshiftitem.AttendanceGroupShiftItemDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord.AttendancePunchRecordDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.scheduling.AttendanceSchedulingDO;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.fixed.AttendanceFixedMapper;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.groupshift.AttendanceGroupShiftMapper;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.groupshiftitem.AttendanceGroupShiftItemMapper;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.punchrecord.AttendancePunchRecordMapper;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.scheduling.AttendanceSchedulingMapper;
import cn.iocoder.yudao.module.system.service.attendance.group.AttendanceGroupService;
import cn.iocoder.yudao.module.system.service.attendance.punchrecord.AttendancePunchRecordService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
@ -57,7 +60,12 @@ public class AttendanceGroupShiftServiceImpl implements AttendanceGroupShiftServ
private AttendanceGroupService attendanceGroupService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
@Lazy
private AttendancePunchRecordMapper attendancePunchRecordMapper;
@Resource
@Lazy
private AttendancePunchRecordService attendancePunchRecordService;
@Override
@Transactional(rollbackFor = Exception.class)
@ -131,8 +139,6 @@ public class AttendanceGroupShiftServiceImpl implements AttendanceGroupShiftServ
// -- 查询下有没有考勤组在用这个班次 - 如果有的话就要考虑更新 是立即更新 还是 次日更新
List<Long> useGroupIds = attendanceGroupService.getUseGroupIds(updateReqVO.getId());
// TODO: 2024/6/5 如果考勤组没有使用班次 则不需要更新
boolean flag = false;
if (CollectionUtil.isEmpty(oldItems)) {
@ -224,13 +230,27 @@ public class AttendanceGroupShiftServiceImpl implements AttendanceGroupShiftServ
}
@Override
@Transactional(rollbackFor = Exception.class)
public void effectiveImmediately(List<Long> groupIds) {
String time = LocalDateTime.now().format(Constants.REPO_DATE_FORMAT);
LocalDateTime now = LocalDateTime.now();
String time = now.format(Constants.REPO_DATE_FORMAT);
String yesterdayStr = LocalDateTimeUtil.offset(now, 1, ChronoUnit.DAYS).format(Constants.REPO_DATE_FORMAT);
// TODO: 2024/6/5 删除当天redis中考勤记录 - 重新计算新的考勤记录到redis 数据库中
for (Long groupId : groupIds) {
String key = Constants.ATTENDANCE + Constants.UNDERLINE + groupId + Constants.UNDERLINE; // + 时间
stringRedisTemplate.delete(key + time);
stringRedisTemplate.delete(key + yesterdayStr);
// -- 生成新的考勤记录到redis中 - 和数据库中数据库中会有什么问题 - 可能会导致两条数据一起更新 旧的数据和新的数据一起更新
// -- 如果不生成会导致什么问题 - 可能导致考勤组人员缺卡 从而导致统计的时候缺卡统计不到
}
// TODO: 2024/6/6 这里如果慢的话可以异步处理
// -- 删除当前考勤组预留的考勤记录
attendancePunchRecordMapper.delete(new LambdaQueryWrapper<AttendancePunchRecordDO>()
.in(AttendancePunchRecordDO::getAttendanceGroupId, groupIds)
.eq(AttendancePunchRecordDO::getNextDayFlag, Constants.TRUE));
// -- 重新计算 - 插入到redis 和数据中
attendancePunchRecordService.defaultPersistence(groupIds, LocalDateTime.now());
}
private List<AttendanceGroupShiftVO> buildShift(List<AttendanceGroupShiftDO> dos) {

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.service.attendance.groupuser;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.groupuser.vo.AttendanceGroupUserCreateOrDelVO;
import cn.iocoder.yudao.module.system.controller.admin.groupuser.vo.AttendanceGroupUserPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.groupuser.vo.AttendanceGroupUserSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupuser.AttendanceGroupUserDO;
@ -21,7 +22,7 @@ public interface AttendanceGroupUserService {
* @param attendanceGroupId
* @param userIds
*/
void createOrDel(Long attendanceGroupId, List<Long> userIds);
AttendanceGroupUserCreateOrDelVO createOrDel(Long attendanceGroupId, List<Long> userIds);
/**
* 更新考勤组人员
@ -60,4 +61,11 @@ public interface AttendanceGroupUserService {
* @return
*/
List<Long> getUserIdsByGroupId(Long id);
/**
* 人员调整立即生效
*
* @param vo
*/
void effectiveImmediately(AttendanceGroupUserCreateOrDelVO vo);
}

View File

@ -1,18 +1,28 @@
package cn.iocoder.yudao.module.system.service.attendance.groupuser;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.iocoder.yudao.framework.common.Constants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.controller.admin.groupuser.vo.AttendanceGroupUserCreateOrDelVO;
import cn.iocoder.yudao.module.system.controller.admin.groupuser.vo.AttendanceGroupUserPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.groupuser.vo.AttendanceGroupUserSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupuser.AttendanceGroupUserDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord.AttendancePunchRecordDO;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.groupuser.AttendanceGroupUserMapper;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.punchrecord.AttendancePunchRecordMapper;
import cn.iocoder.yudao.module.system.service.attendance.punchrecord.AttendancePunchRecordService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@ -30,39 +40,40 @@ public class AttendanceGroupUserServiceImpl implements AttendanceGroupUserServic
@Resource
private AttendanceGroupUserMapper groupUserMapper;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private AttendancePunchRecordMapper attendancePunchRecordMapper;
@Resource
private AttendancePunchRecordService attendancePunchRecordService;
@Override
public void createOrDel(Long attendanceGroupId, List<Long> userIds) {
public AttendanceGroupUserCreateOrDelVO createOrDel(Long attendanceGroupId, List<Long> userIds) {
AttendanceGroupUserCreateOrDelVO vo = new AttendanceGroupUserCreateOrDelVO();
List<AttendanceGroupUserDO> groupUserList = groupUserMapper.selectList(AttendanceGroupUserDO::getAttendanceGroupId, attendanceGroupId);
List<AttendanceGroupUserDO> list = new ArrayList<>();
if (CollectionUtil.isEmpty(groupUserList)) {
if (CollectionUtil.isNotEmpty(userIds)) {
for (Long userId : userIds) {
list.add(new AttendanceGroupUserDO().setUserId(userId).setAttendanceGroupId(attendanceGroupId));
}
groupUserMapper.insertBatch(list);
}
} else {
if (CollectionUtil.isNotEmpty(userIds)) {
List<Long> oldUserIds = groupUserList.stream().map(AttendanceGroupUserDO::getUserId).collect(Collectors.toList());
List<Long> delIds = new ArrayList<>(CollectionUtil.subtract(oldUserIds, userIds));
List<Long> saveIds = new ArrayList<>(CollectionUtil.subtract(userIds, oldUserIds));
if (CollectionUtil.isNotEmpty(delIds)) {
groupUserMapper.delete(new LambdaQueryWrapper<AttendanceGroupUserDO>().eq(AttendanceGroupUserDO::getAttendanceGroupId, attendanceGroupId)
.in(AttendanceGroupUserDO::getUserId, delIds));
}
if (CollectionUtil.isNotEmpty(saveIds)) {
for (Long userId : saveIds) {
list.add(new AttendanceGroupUserDO().setUserId(userId).setAttendanceGroupId(attendanceGroupId));
}
groupUserMapper.insertBatch(list);
}
} else {
groupUserMapper.delete(AttendanceGroupUserDO::getAttendanceGroupId, attendanceGroupId);
}
if (CollectionUtil.isEmpty(userIds)) {
userIds = Collections.emptyList();
}
List<Long> oldUserIds = groupUserList.stream().map(AttendanceGroupUserDO::getUserId).collect(Collectors.toList());
List<Long> delIds = new ArrayList<>(CollectionUtil.subtract(oldUserIds, userIds));
List<Long> saveIds = new ArrayList<>(CollectionUtil.subtract(userIds, oldUserIds));
if (CollectionUtil.isNotEmpty(delIds)) {
groupUserMapper.delete(new LambdaQueryWrapper<AttendanceGroupUserDO>().eq(AttendanceGroupUserDO::getAttendanceGroupId, attendanceGroupId)
.in(AttendanceGroupUserDO::getUserId, delIds));
}
if (CollectionUtil.isNotEmpty(saveIds)) {
for (Long userId : saveIds) {
list.add(new AttendanceGroupUserDO().setUserId(userId).setAttendanceGroupId(attendanceGroupId));
}
groupUserMapper.insertBatch(list);
}
vo.setAttendanceGroupId(attendanceGroupId);
vo.setDelUserIds(delIds);
vo.setSaveUserIds(saveIds);
return vo;
}
@Override
@ -105,4 +116,25 @@ public class AttendanceGroupUserServiceImpl implements AttendanceGroupUserServic
return list.stream().map(AttendanceGroupUserDO::getUserId).collect(Collectors.toList());
}
@Override
public void effectiveImmediately(AttendanceGroupUserCreateOrDelVO vo) {
LocalDateTime now = LocalDateTime.now();
String time = now.format(Constants.REPO_DATE_FORMAT);
String yesterdayStr = LocalDateTimeUtil.offset(now, 1, ChronoUnit.DAYS).format(Constants.REPO_DATE_FORMAT);
// 删除掉redis 中的记录 -
if (CollectionUtil.isNotEmpty(vo.getDelUserIds())) {
String key = Constants.ATTENDANCE + Constants.UNDERLINE + vo.getAttendanceGroupId() + Constants.UNDERLINE; // + 时间
stringRedisTemplate.opsForHash().delete(key + time, vo.getDelUserIds().stream().map(String::valueOf).toArray(String[]::new));
stringRedisTemplate.opsForHash().delete(key + yesterdayStr, vo.getDelUserIds().stream().map(String::valueOf).toArray(String[]::new));
//删除掉这些人员在这个考勤组里的记录
attendancePunchRecordMapper.delete(new LambdaQueryWrapper<AttendancePunchRecordDO>()
.eq(AttendancePunchRecordDO::getAttendanceGroupId, vo.getAttendanceGroupId())
.in(AttendancePunchRecordDO::getUserId, vo.getDelUserIds())
.eq(AttendancePunchRecordDO::getNextDayFlag, Constants.TRUE));
}
if (CollectionUtil.isNotEmpty(vo.getSaveUserIds())) {
attendancePunchRecordService.defaultPersistence(vo.getAttendanceGroupId(), vo.getSaveUserIds(), LocalDateTime.now());
}
}
}

View File

@ -3,10 +3,15 @@ package cn.iocoder.yudao.module.system.service.attendance.punchrecord;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.controller.admin.punchrecord.vo.AttendancePunchRecordPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.punchrecord.vo.AttendancePunchRecordSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.group.AttendanceGroupDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupshiftitem.AttendanceGroupShiftItemDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord.AttendancePunchRecordDO;
import org.springframework.transaction.annotation.Transactional;
import javax.validation.Valid;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* 用户打卡记录 Service 接口
@ -59,4 +64,34 @@ public interface AttendancePunchRecordService {
* @param attendancePunchRecordDOList
*/
void saveBatch(List<AttendancePunchRecordDO> attendancePunchRecordDOList);
/**
* 预设考勤
* @param groupId
* @param userIds
* @param localDateTime
*/
void defaultPersistence(Long groupId, List<Long> userIds, LocalDateTime localDateTime);
/**
* 预设考勤
*
* @param groupIds 考勤组ids
* @param localDateTime 时间
*/
void defaultPersistence(List<Long> groupIds, LocalDateTime localDateTime);
/**
* 预设持久化考勤
*
* @param map 获取到考勤组 - 班次 key/value 格式
* @param groupMap 考勤组id分map
* @param groupUserMap 考勤组id分组用户列表
* @param itemMaps 考勤班次子表根据考勤班次分组
* @param time 时间格式 yyyy-MM-dd
* @param localDateTime 需要预设的时间
*/
void defaultPersistence(Map<Long, Long> map, Map<Long, AttendanceGroupDO> groupMap, Map<Long, List<Long>> groupUserMap,
Map<Long, List<AttendanceGroupShiftItemDO>> itemMaps, String time, LocalDateTime localDateTime);
}

View File

@ -1,17 +1,45 @@
package cn.iocoder.yudao.module.system.service.attendance.punchrecord;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.Constants;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.controller.admin.punchrecord.vo.AttendancePunchRecordPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.punchrecord.vo.AttendancePunchRecordSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.group.AttendanceGroupDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupshiftitem.AttendanceGroupShiftItemDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupuser.AttendanceGroupUserDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord.AttendancePunchRecordDO;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.group.AttendanceGroupMapper;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.groupuser.AttendanceGroupUserMapper;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.punchrecord.AttendancePunchRecordMapper;
import cn.iocoder.yudao.module.system.service.attendance.AttendanceService;
import cn.iocoder.yudao.module.system.service.attendance.fixed.AttendanceFixedService;
import cn.iocoder.yudao.module.system.service.attendance.groupshiftitem.AttendanceGroupShiftItemService;
import cn.iocoder.yudao.module.system.service.attendance.punch.dto.AttendanceOnTheDayDTO;
import cn.iocoder.yudao.module.system.service.attendance.scheduling.AttendanceSchedulingService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.PUNCH_RECORD_NOT_EXISTS;
@ -27,6 +55,22 @@ public class AttendancePunchRecordServiceImpl implements AttendancePunchRecordSe
@Resource
private AttendancePunchRecordMapper punchRecordMapper;
@Resource
@Lazy // 避免依赖循环
private AttendanceService attendanceService;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private AttendanceFixedService attendanceFixedService;
@Resource
private AttendanceSchedulingService attendanceSchedulingService;
@Resource
private AttendanceGroupShiftItemService attendanceGroupShiftItemService;
@Resource
private AttendanceGroupUserMapper attendanceGroupUserMapper;
@Resource
private AttendanceGroupMapper attendanceGroupMapper;
@Override
public Long createPunchRecord(AttendancePunchRecordSaveReqVO createReqVO) {
@ -76,4 +120,134 @@ public class AttendancePunchRecordServiceImpl implements AttendancePunchRecordSe
punchRecordMapper.insertBatch(attendancePunchRecordDOList);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void defaultPersistence(Long groupId, List<Long> userIds, LocalDateTime localDateTime) {
if (groupId == null || CollectionUtil.isEmpty(userIds)) {
return;
}
AttendanceGroupDO attendanceGroupDO = attendanceGroupMapper.selectById(groupId);
List<AttendanceGroupDO> attendanceGroupDOS = new ArrayList<>();
attendanceGroupDOS.add(attendanceGroupDO);
List<AttendanceGroupUserDO> attendanceGroupUserDOS = attendanceGroupUserMapper.selectList(new LambdaQueryWrapper<AttendanceGroupUserDO>()
.eq(AttendanceGroupUserDO::getAttendanceGroupId, groupId)
.in(AttendanceGroupUserDO::getUserId, userIds));
this.defaultPersistence(attendanceGroupDOS, attendanceGroupUserDOS, localDateTime);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void defaultPersistence(List<Long> groupIds, LocalDateTime localDateTime) {
List<AttendanceGroupDO> attendanceGroupDOS;
List<AttendanceGroupUserDO> attendanceGroupUserDOS;
if (CollectionUtil.isNotEmpty(groupIds)) {
attendanceGroupDOS = attendanceGroupMapper.selectBatchIds(groupIds);
attendanceGroupUserDOS = attendanceGroupUserMapper.selectList(new LambdaQueryWrapper<AttendanceGroupUserDO>()
.in(AttendanceGroupUserDO::getAttendanceGroupId, groupIds));
} else {
attendanceGroupDOS = attendanceGroupMapper.selectList();
attendanceGroupUserDOS = attendanceGroupUserMapper.selectList();
}
this.defaultPersistence(attendanceGroupDOS, attendanceGroupUserDOS, localDateTime);
}
public void defaultPersistence(List<AttendanceGroupDO> attendanceGroupDOS, List<AttendanceGroupUserDO> attendanceGroupUserDOS, LocalDateTime localDateTime) {
// 获取所有考勤组
// -- 根据考勤组ids 获取所有人员
//将attendanceGroupUserDOS对象中的type相同的userId合并成一个数组 type作为key 用户id列表List<Long>作为value
Map<Long, List<Long>> groupUserMap = attendanceGroupUserDOS.stream().collect(Collectors.groupingBy(AttendanceGroupUserDO::getAttendanceGroupId,
Collectors.mapping(AttendanceGroupUserDO::getUserId, Collectors.toList())));
// -- 获取考勤组下考勤规则 - 将将考勤组分组 - 按类型
String time = localDateTime.format(Constants.REPO_DATE_FORMAT);
// 获取到考勤组 - 班次 key/value 格式
Map<Long, Long> map = this.getAttendanceGroupShiftIdGroupByGroup(attendanceGroupDOS, localDateTime);
Map<Long, AttendanceGroupDO> groupMap = attendanceGroupDOS.stream().collect(Collectors.toMap(AttendanceGroupDO::getId, v -> v));
// -- 获取班次子表列表
if (MapUtil.isNotEmpty(map)) {
List<AttendanceGroupShiftItemDO> attendanceGroupShiftItemDOList = attendanceGroupShiftItemService.getGroupShiftItemListByShiftIds(new ArrayList<>(map.values()));
Map<Long, List<AttendanceGroupShiftItemDO>> itemMaps = attendanceGroupShiftItemDOList.stream().collect(Collectors.groupingBy(AttendanceGroupShiftItemDO::getKqAttendanceGroupShiftId));
this.defaultPersistence(map, groupMap, groupUserMap, itemMaps, time, localDateTime);
}
}
/**
* @param map 获取到考勤组 - 班次 key/value 格式
* @param groupMap 考勤组id分map
* @param groupUserMap 考勤组id分组用户列表
* @param itemMaps 考勤班次子表根据考勤班次分组
* @param time 时间格式 yyyy-MM-dd
* @param localDateTime 需要预设的时间
*/
@Override
public void defaultPersistence(Map<Long, Long> map, Map<Long, AttendanceGroupDO> groupMap, Map<Long, List<Long>> groupUserMap,
Map<Long, List<AttendanceGroupShiftItemDO>> itemMaps, String time, LocalDateTime localDateTime) {
List<AttendancePunchRecordDO> attendancePunchRecordDOList = new ArrayList<>();
LocalDateTime nextDayLocalDateTime = LocalDateTimeUtil.offset(localDateTime, 1, ChronoUnit.DAYS);
for (Map.Entry<Long, Long> entry : map.entrySet()) {
String key = Constants.ATTENDANCE + Constants.UNDERLINE + entry.getKey() + Constants.UNDERLINE; // + 时间
AttendanceGroupDO attendanceGroupDO = groupMap.get(entry.getKey());
List<Long> userIds = groupUserMap.get(entry.getKey());
if (CollectionUtil.isEmpty(userIds)) {
continue;
}
List<AttendanceGroupShiftItemDO> attendanceGroupShiftItemDOS = itemMaps.get(entry.getValue());
if (CollectionUtil.isEmpty(attendanceGroupShiftItemDOS)) {
continue;
}
List<AttendanceOnTheDayDTO> attendanceOnTheDayDTOS = attendanceService.buildAttendanceOnTheDay(attendanceGroupShiftItemDOS);
for (Long userId : userIds) {
for (AttendanceOnTheDayDTO attendanceOnTheDayDTO : attendanceOnTheDayDTOS) {
AttendancePunchRecordDO attendancePunchRecordDO = new AttendancePunchRecordDO();
attendancePunchRecordDO.setUserId(userId);
attendancePunchRecordDO.setAttendanceGroupId(entry.getKey());
attendancePunchRecordDO.setAttendanceGroupName(attendanceGroupDO.getGroupName());
attendancePunchRecordDO.setAttendanceGroupShiftId(attendanceOnTheDayDTO.getKqAttendanceGroupShiftId());
attendancePunchRecordDO.setAttendanceGroupShiftName(attendanceOnTheDayDTO.getKqAttendanceGroupShiftName());
attendancePunchRecordDO.setAttendanceGroupShiftItemId(attendanceOnTheDayDTO.getId());
attendancePunchRecordDO.setType(attendanceGroupDO.getType());
attendancePunchRecordDO.setPunchType(attendanceGroupDO.getPunchType());
attendancePunchRecordDO.setWorkType(attendanceOnTheDayDTO.getType());
attendancePunchRecordDO.setLevel(attendanceOnTheDayDTO.getLevel());
attendancePunchRecordDO.setStatus(AttendanceOnTheDayDTO.PUNCH_STATUS_UN_PUNCH);
attendancePunchRecordDO.setFieldServiceFlag(Constants.FALSE);
attendancePunchRecordDO.setNextDayFlag(Constants.TRUE);
attendancePunchRecordDO.setDayTime(time);
LocalDateTime shouldPunchTime = LocalDateTime.ofInstant(DateUtils.buildHHmmTime(attendanceOnTheDayDTO.getTime(),
(attendanceOnTheDayDTO.getNextDayFlag() == 0 ? localDateTime : nextDayLocalDateTime)).toInstant(), ZoneId.systemDefault());
attendancePunchRecordDO.setShouldPunchTime(shouldPunchTime);
attendancePunchRecordDO.setLatestPunchTime(shouldPunchTime.plusMinutes(attendanceOnTheDayDTO.getAfterPunchTime()));
attendancePunchRecordDOList.add(attendancePunchRecordDO);
}
stringRedisTemplate.opsForHash().put(key + time, userId, JSONUtil.toJsonStr(attendanceOnTheDayDTOS));
}
//设置缓存 2天
stringRedisTemplate.expire(key + time, 2, TimeUnit.DAYS);
}
// -- 批量
this.saveBatch(attendancePunchRecordDOList);
}
private Map<Long, Long> getAttendanceGroupShiftIdGroupByGroup(List<AttendanceGroupDO> attendanceGroupDOS, LocalDateTime localDateTime) {
Map<Long, Long> map = new HashMap<>();
List<AttendanceGroupDO> fixedList = attendanceGroupDOS.stream().filter(a -> a.getType().equals(1)).collect(Collectors.toList());
List<AttendanceGroupDO> schedulingList = attendanceGroupDOS.stream().filter(a -> a.getType().equals(2)).collect(Collectors.toList());
if (!fixedList.isEmpty()) {
Map<Long, Long> attendanceFixedMap = attendanceFixedService.getGroupToShiftIdMap(fixedList, localDateTime);
if (ObjectUtil.isNotEmpty(attendanceFixedMap)) {
map.putAll(attendanceFixedMap);
}
}
if (!schedulingList.isEmpty()) {
Map<Long, Long> attendanceSchedulingMap = attendanceSchedulingService.getGroupToShiftIdMap(schedulingList, localDateTime);
if (ObjectUtil.isNotEmpty(attendanceSchedulingMap)) {
map.putAll(attendanceSchedulingMap);
}
}
return map;
}
}