设备打卡调整

This commit is contained in:
aikai 2024-06-04 14:02:03 +08:00
parent 2f821a76b1
commit c501dea1f4
24 changed files with 341 additions and 58 deletions

View File

@ -11,6 +11,7 @@ import cn.hutool.json.JSONUtil;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@ -139,6 +140,27 @@ public class DateUtils {
return calendar.getTime();
}
/**
* 创建指定时间
*
* @param timeStr 时间字符串格式为 HH:mm
* @return 指定时间
*/
public static Date buildHHmmTime(String timeStr, LocalDateTime localDateTime) {
// 将LocalDateTime转换为Instant因为Calendar不能直接从LocalDateTime构造
Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
// 使用Instant创建Calendar对象
Calendar calendar = Calendar.getInstance();
calendar.setTimeInMillis(instant.toEpochMilli());
// 输出转换后的Calendar对象
String[] time = timeStr.split(":");
calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(time[0]));
calendar.set(Calendar.MINUTE, Integer.parseInt(time[1]));
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
return calendar.getTime();
}
/**
* 创建指定时间
*
@ -152,7 +174,6 @@ public class DateUtils {
calendar.set(Calendar.MINUTE, Integer.parseInt(time[1]));
calendar.set(Calendar.SECOND, 0);
calendar.set(Calendar.MILLISECOND, 0);
Date endTime = calendar.getTime();
return calendar.getTime();
}

View File

@ -93,7 +93,7 @@ public class TenantSecurityWebFilter extends ApiRequestFilter {
// StringBuffer url = request.getRequestURL();
// log.info("请求URL地址" + url.toString());
// log.info("请求URI地址" +request.getRequestURI());
log.error("[doFilterInternal][URL({}/{}) 未传递租户编号]", request.getRequestURI(), request.getMethod());
//log.error("[doFilterInternal][URL({}/{}) 未传递租户编号]", request.getRequestURI(), request.getMethod());
ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),
"租户的请求未传递,请进行排查"));
return;

View File

@ -197,6 +197,12 @@ public interface ErrorCodeConstants {
ErrorCode NO_PERMISSION_TO_VIEW_CURRENT_ATTENDANCE_GROUP_INFORMATION = new ErrorCode(1_003_012_000, "无权限查看当前考勤组信息!");
ErrorCode THE_ATTENDANCE_INTERVAL_SHALL_NOT_EXCEED24_HOURS = new ErrorCode(1_003_013_000, "考勤区间不得超过24小时");
ErrorCode GROUP_SHIFT_IN_USE = new ErrorCode(1_003_014_000, "班次使用中不允许删除");
ErrorCode LOG_FORM_NOT_USE = new ErrorCode(1_009_010_004, "你不用使用该日志模板");
ErrorCode LOG_USE_NOT_EXISTS = new ErrorCode(1_009_010_005, "模板不存在");

View File

@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.system.controller;
import cn.hutool.json.JSONObject;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.system.controller.app.attendance.dto.AttendancePunchDTO;
import cn.iocoder.yudao.module.system.controller.app.attendance.vo.AttendancePunchVO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.group.AttendanceGroupDO;
import cn.iocoder.yudao.module.system.service.attendance.AttendanceService;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
@Tag(name = "管理后台 - 资产")
@RestController
@RequestMapping("/api/v1")
@Validated
@Slf4j
public class AttendanceEquipmentController {
@Resource
private AttendanceService attendanceService;
@PostMapping("/verify_user")
@PermitAll
public JSONObject verifyUser(@RequestBody JSONObject object) {
TenantContextHolder.setTenantId(1L);
// TODO: 2024/6/4 暂时写死
JSONObject result = new JSONObject().set("Result", 0).set("Msg", "识别通过");
JSONObject content = new JSONObject();
content.set("voice_code", -2);
result.set("Content", content);
try {
String sn = object.getStr("sn");
String userId = object.getStr("user_id");
content.set("user_id", userId);
AttendancePunchVO punch = attendanceService.punch(new AttendancePunchDTO()
.setUserId(Long.valueOf(userId))
.setPunchType(AttendanceGroupDO.PUNCH_TYPE_ATTENDANCE_MACHINE)
.setSn(sn));
content.set("voice_text", punch.getStatus() == 0 ? "打卡成功" : (punch.getStatus() == 1 ? "迟到打卡成功" : "早退打卡成功"));
} catch (ServiceException e) {
content.set("voice_text", e.getMessage());
} catch (Exception e) {
content.set("voice_text", "系统错误");
} finally {
result.set("Content", content);
return result;
}
}
}

View File

@ -87,6 +87,14 @@ public class AttendanceGroupController {
return success(BeanUtils.toBean(pageResult, AttendanceGroupRespVO.class));
}
@GetMapping("/getAll")
@Operation(summary = "获得所有考勤组")
public CommonResult<List<AttendanceGroupRespVO>> getAll() {
List<AttendanceGroupDO> pageResult = groupService.getAll();
return success(BeanUtils.toBean(pageResult, AttendanceGroupRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出考勤组 Excel")
@PreAuthorize("@ss.hasPermission('attendance:group:export')")

View File

@ -165,6 +165,15 @@ public class UserController {
return success(user);
}
@PostMapping("/selectByDeptIdsFilterGroupUser")
@Operation(summary = "通过部门ids获取用户信息列表(过滤掉已在考勤组的用户)")
@PreAuthorize("@ss.hasPermission('system:user:query')")
public CommonResult<List<UserRespVO>> selectByDeptIdsFilterGroupUser(@RequestBody Collection<Long> deptIds) {
List<UserRespVO> user = userService.selectByDeptIdsFilterGroupUser(deptIds);
return success(user);
}
@PostMapping("/getByDeptId")
@Operation(summary = "根据部门编号获得用户详情")
@Parameter(name = "deptId", description = "部门编号", required = true, example = "117")

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.system.controller.app.attendance.dto;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.group.AttendanceGroupDO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -33,6 +32,14 @@ public class AttendancePunchDTO {
* 当前用户id
*/
private Long userId = getLoginUserId();
/**
* 打卡类型 1考勤机 2小程序范围打卡(默认)
*/
private Integer punchType = AttendanceGroupDO.PUNCH_TYPE_MI_NI_APP;
/**
* 考勤机编号
*/
private String sn;
/**
* 当前时间
*/

View File

@ -23,7 +23,10 @@ public class AttendancePunchPageDTO {
* 当前用户id
*/
private Long userId = getLoginUserId();
/**
* 打卡类型 1考勤机 2小程序范围打卡(默认)
*/
private Integer punchType = AttendanceGroupDO.PUNCH_TYPE_MI_NI_APP;
/**
* 是否返回考勤组考勤班次信息
*/

View File

@ -20,4 +20,7 @@ public class AttendancePunchVO {
@Schema(description = "当天考勤列表")
private List<AttendanceOnTheDayDTO> attendanceOnTheDayDTOS;
@Schema(description = "打卡状态 0正常 1迟到 2早退", example = "1")
private Integer status;
}

View File

@ -53,7 +53,7 @@ public class AttendanceGroupShiftItemDO extends BaseDO {
*/
private Integer mustFlag;
/**
* 是否次日(开始时间 大于 结束时间)跨天 0否 1
* 是否次日(开始时间 大于 结束时间)跨天 0否 1跨天 2结束时间跨天
*/
private Integer nextDayFlag;
/**

View File

@ -1,15 +1,12 @@
package cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord;
import io.swagger.v3.oas.annotations.media.Schema;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import java.time.LocalDateTime;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
/**
* 用户打卡记录 DO
@ -100,6 +97,10 @@ public class AttendancePunchRecordDO extends BaseDO {
* 应打卡时间
*/
private LocalDateTime shouldPunchTime;
/**
* 最晚打卡时间(超过即为缺卡)
*/
private LocalDateTime latestPunchTime;
/**
* 打卡备注
*/

View File

@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.punchrecord.vo.AttendancePunchRecordPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord.AttendancePunchRecordDO;
import cn.iocoder.yudao.module.system.service.attendance.punch.dto.AttendanceOnTheDayDTO;
import org.apache.ibatis.annotations.Mapper;
/**
@ -23,6 +24,8 @@ public interface AttendancePunchRecordMapper extends BaseMapperX<AttendancePunch
.eqIfPresent(AttendancePunchRecordDO::getType, reqVO.getType())
.eqIfPresent(AttendancePunchRecordDO::getPunchType, reqVO.getPunchType())
.eqIfPresent(AttendancePunchRecordDO::getStatus, reqVO.getStatus())
.eqIfPresent(AttendancePunchRecordDO::getWorkType, reqVO.getWorkType())
.neIfPresent(AttendancePunchRecordDO::getStatus, AttendanceOnTheDayDTO.PUNCH_STATUS_UN_PUNCH)
.eqIfPresent(AttendancePunchRecordDO::getFieldServiceFlag, reqVO.getFieldServiceFlag())
.betweenIfPresent(AttendancePunchRecordDO::getDayTime, reqVO.getDayTime())
.betweenIfPresent(AttendancePunchRecordDO::getPunchTime, reqVO.getPunchTime())

View File

@ -65,12 +65,20 @@ public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
return selectList(new LambdaQueryWrapperX<AdminUserDO>()
.eq(AdminUserDO::getDeptId, deptId)
.ne(AdminUserDO::getId, userId)
.eq(AdminUserDO::getUserType , 1));
.eq(AdminUserDO::getUserType, 1));
}
void emptyOpenId(@Param("openId") String openId);
List<UserRespVO> selectByDeptIds(Collection<Long> deptIds);
/**
* 根据部门ids查询出用户列表 过滤掉考勤组里有的
*
* @param deptIds
* @return
*/
List<UserRespVO> selectByDeptIdsFilterGroupUser(Collection<Long> deptIds);
List<Long> selectUserByBoss();
}

View File

@ -1,8 +1,10 @@
package cn.iocoder.yudao.module.system.job.attendance;
import cn.iocoder.yudao.framework.common.Constants;
import cn.iocoder.yudao.framework.tenant.core.job.TenantJob;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord.AttendancePunchRecordDO;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.punchrecord.AttendancePunchRecordMapper;
import cn.iocoder.yudao.module.system.service.attendance.punchrecord.AttendancePunchRecordService;
import cn.iocoder.yudao.module.system.service.attendance.punch.dto.AttendanceOnTheDayDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
@ -10,6 +12,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.time.LocalDateTime;
@Component
@Slf4j
@ -24,7 +27,16 @@ public class AttendanceMissingCardJob {
@TenantJob // --- 这个注解 会将租户列表拉出来 完了后逐个租户执行 定时任务需要注意
public ReturnT<String> execute() throws Exception {
log.info("开始 修改考勤缺卡");
attendancePunchRecordMapper.selectList(new LambdaQueryWrapper<>());
LocalDateTime now = LocalDateTime.now();
String time = now.format(Constants.REPO_DATE_FORMAT);
LambdaQueryWrapper<AttendancePunchRecordDO> le = new LambdaQueryWrapper<AttendancePunchRecordDO>()
.eq(AttendancePunchRecordDO::getDayTime, time)
.eq(AttendancePunchRecordDO::getNextDayFlag, Constants.TRUE)
.eq(AttendancePunchRecordDO::getStatus, AttendanceOnTheDayDTO.PUNCH_STATUS_UN_PUNCH)
.le(AttendancePunchRecordDO::getLatestPunchTime, now);
AttendancePunchRecordDO attendancePunchRecordDO = new AttendancePunchRecordDO();
attendancePunchRecordDO.setStatus(AttendanceOnTheDayDTO.PUNCH_STATUS_MISS);
attendancePunchRecordMapper.update(attendancePunchRecordDO, le);
log.info("结束 修改考勤缺卡");
// 返回执行成功
return ReturnT.SUCCESS;

View File

@ -80,6 +80,7 @@ public class AttendanceStatisticsJob {
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);
@ -117,8 +118,10 @@ public class AttendanceStatisticsJob {
attendancePunchRecordDO.setFieldServiceFlag(Constants.FALSE);
attendancePunchRecordDO.setNextDayFlag(Constants.TRUE);
attendancePunchRecordDO.setDayTime(time);
LocalDateTime shouldPunchTime = LocalDateTime.ofInstant(DateUtils.buildHHmmTime(attendanceOnTheDayDTO.getTime()).toInstant(), ZoneId.systemDefault());
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);
}
}

View File

@ -112,8 +112,9 @@ public class AttendanceServiceImpl implements AttendanceService {
@Override
@Transactional(rollbackFor = Exception.class)
public AttendancePunchVO punch(AttendancePunchDTO dto) {
AttendancePunchPageVO pageVO = this.getPunchPage(new AttendancePunchPageDTO().setLongitude(dto.getLongitude())
.setLatitude(dto.getLatitude()).setFlag(true).setLocalDateTime(dto.getLocalDateTime()).setUserId(dto.getUserId()));
AttendancePunchPageVO pageVO = this.getPunchPage(new AttendancePunchPageDTO().setPunchType(dto.getPunchType())
.setLongitude(dto.getLongitude()).setLatitude(dto.getLatitude())
.setFlag(true).setLocalDateTime(dto.getLocalDateTime()).setUserId(dto.getUserId()));
// -- 获取当天的考勤记录
if (Constants.FALSE.equals(pageVO.getInGroup())) {
throw exception(NOT_IN_THE_ATTENDANCE_GROUP);
@ -141,7 +142,7 @@ public class AttendanceServiceImpl implements AttendanceService {
.setAttendanceGroupShiftId(pageVO.getAttendanceGroupShiftDO().getId())
.setAttendanceGroupShiftItemId(pageVO.getAttendanceGroupShiftItemId())
.setType(pageVO.getActivationGroup().getType())
.setPunchType(AttendanceGroupDO.PUNCH_TYPE_MI_NI_APP)
.setPunchType(dto.getPunchType())
.setWorkType(pageVO.getType())
.setLevel(dayDTO.getLevel())
.setStatus(status)
@ -181,6 +182,7 @@ public class AttendanceServiceImpl implements AttendanceService {
dayDTO.setPunchAddress(dto.getPunchAddress());
dayDTO.setRemark(dto.getRemark());
dayDTO.setImage(dto.getImage());
// TODO: 2024/5/31 可能会有问题
dayDTO.setShouldPunchTime(pageVO.getShouldPunchTime());
dayDTO.setPunchLocalDateTime(dto.getLocalDateTime());
dayDTO.setLateTime(attendancePunchRecordSaveReqVO.getLateTime());
@ -189,6 +191,7 @@ public class AttendanceServiceImpl implements AttendanceService {
String mapKey = dto.getUserId().toString();
stringRedisTemplate.opsForHash().put(pageVO.getRedisKey(), mapKey, JSONUtil.toJsonStr(attendanceOnTheDayDTOS));
}
vo.setStatus(status);
vo.setActivationGroup(pageVO.getActivationGroup());
vo.setAttendanceGroupShiftDO(pageVO.getAttendanceGroupShiftDO());
vo.setAttendanceOnTheDayDTOS(attendanceOnTheDayDTOS);
@ -222,9 +225,10 @@ public class AttendanceServiceImpl implements AttendanceService {
LocalDateTime localDateTime = dto.getLocalDateTime();
AttendanceGroupDO activationGroup = dto.getActivationGroup();
vo.setFieldworkFlag(activationGroup.getFieldworkFlag());
// - 根据经纬度判断是否在对应班组的打卡点上
vo.setPunchPoint(GeoUtil.distance(Double.parseDouble(dto.getLatitude()), Double.parseDouble(dto.getLongitude())
, Double.parseDouble(activationGroup.getLatitude()), Double.parseDouble(activationGroup.getLongitude()), activationGroup.getScope()));
// - 根据经纬度判断是否在对应班组的打卡点上 - 如果是考勤机的话默认就是在打卡点
vo.setPunchPoint(AttendanceGroupDO.PUNCH_TYPE_ATTENDANCE_MACHINE.equals(dto.getPunchType()) ? Constants.TRUE :
GeoUtil.distance(Double.parseDouble(dto.getLatitude()), Double.parseDouble(dto.getLongitude())
, Double.parseDouble(activationGroup.getLatitude()), Double.parseDouble(activationGroup.getLongitude()), activationGroup.getScope()));
// -- 获取班次
AttendanceGroupShiftDO attendanceGroupShiftDO = attendanceGroupShiftService.getGroupShift(vo.getAttendanceGroupShiftId());
@ -236,21 +240,17 @@ public class AttendanceServiceImpl implements AttendanceService {
String yesterdayStr = LocalDateTimeUtil.offset(localDateTime, -1, ChronoUnit.DAYS).format(Constants.REPO_DATE_FORMAT);
String toDayStr = localDateTime.format(Constants.REPO_DATE_FORMAT);
String targetDayStr = null;
if (attendanceGroupShiftDO.getNextDayFlag() == null || Constants.FALSE.equals(attendanceGroupShiftDO.getNextDayFlag())) {
targetDayStr = toDayStr;
} else {
// 如果跨天的话 所以不能够直接通过判断是否跨天来取redis 数据 - 而是应该获取最晚打下班卡时间判断当前时间是否在最晚打卡下班时间之后 - 如果是的话 插入今天的数据
targetDayStr = yesterdayStr;
// -- 获取最后一个元素
AttendanceGroupShiftItemDO attendanceOnTheDayDTO = CollUtil.getLast(attendanceGroupShiftItemDOList);
// -- 获取最晚下班打卡时间
LocalDateTime endTime = LocalDateTime.ofInstant(DateUtils.buildHHmmTime(attendanceOnTheDayDTO.getEndTime()).toInstant(), ZoneId.systemDefault()).plusMinutes(attendanceOnTheDayDTO.getAfterPunchTimeDownWork());
if (localDateTime.isAfter(endTime)) {
targetDayStr = toDayStr;
String targetDayStr = localDateTime.format(Constants.REPO_DATE_FORMAT);
AttendanceGroupShiftItemDO last = CollUtil.getLast(attendanceGroupShiftItemDOList);
// -- 如果最后的时间是跨天 那么 需要判断当前时间是否在跨天时间段内 - 如果是的话取昨天的的yyyy-MM-dd
if (Arrays.asList(1, 2).contains(last.getNextDayFlag())) {
LocalDateTime endOfDay = LocalDateTime.ofInstant(DateUtils.buildHHmmTime(last.getEndTime()).toInstant(), ZoneId.systemDefault()).plusMinutes(last.getAfterPunchTimeDownWork());
LocalDateTime beginOfDay = LocalDateTimeUtil.beginOfDay(endOfDay);
if (LocalDateTimeUtil.isIn(localDateTime, beginOfDay, endOfDay)) {
targetDayStr = yesterdayStr;
}
}
List<AttendanceOnTheDayDTO> attendanceOnTheDayDTOS = this.getAttendanceOnTheDay(key, mapKey, targetDayStr, attendanceGroupShiftItemDOList);
// -- 这里没有的情况 只可能是redis 数据库崩了/或者班次子表没数据 - 暂时先返回不需要考勤
if (attendanceOnTheDayDTOS.isEmpty()) {
@ -376,6 +376,7 @@ public class AttendanceServiceImpl implements AttendanceService {
dto.setAfterPunchTime(attendanceGroupShiftItemDO.getAfterPunchTimeUpWork());
dto.setPunchStatus(AttendanceOnTheDayDTO.PUNCH_STATUS_UN_PUNCH);
dto.setFieldServiceFlag(Constants.FALSE);
dto.setNextDayFlag(attendanceGroupShiftItemDO.getNextDayFlag() == 0 || attendanceGroupShiftItemDO.getNextDayFlag() == 2 ? 0 : 1);
attendanceOnTheDayDTOS.add(dto);
dto = new AttendanceOnTheDayDTO();
@ -390,6 +391,7 @@ public class AttendanceServiceImpl implements AttendanceService {
dto.setAfterPunchTime(attendanceGroupShiftItemDO.getAfterPunchTimeDownWork());
dto.setPunchStatus(AttendanceOnTheDayDTO.PUNCH_STATUS_UN_PUNCH);
dto.setFieldServiceFlag(Constants.FALSE);
dto.setNextDayFlag(attendanceGroupShiftItemDO.getNextDayFlag() == 0 ? 0 : 1);
attendanceOnTheDayDTOS.add(dto);
}
return attendanceOnTheDayDTOS;

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.group.vo.AttendanceGroupS
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.group.AttendanceGroupDO;
import javax.validation.Valid;
import java.util.List;
/**
* 考勤组 Service 接口
@ -61,4 +62,11 @@ public interface AttendanceGroupService {
AttendanceGroupDO getByUserId(Long userId);
void test();
/**
* 获取所有
*
* @return
*/
List<AttendanceGroupDO> getAll();
}

View File

@ -129,6 +129,8 @@ public class AttendanceGroupServiceImpl implements AttendanceGroupService {
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);
@ -141,11 +143,11 @@ public class AttendanceGroupServiceImpl implements AttendanceGroupService {
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)){
if (CollectionUtil.isEmpty(userIds)) {
continue;
}
List<AttendanceGroupShiftItemDO> attendanceGroupShiftItemDOS = itemMaps.get(entry.getValue());
if (CollectionUtil.isEmpty(attendanceGroupShiftItemDOS)){
if (CollectionUtil.isEmpty(attendanceGroupShiftItemDOS)) {
continue;
}
List<AttendanceOnTheDayDTO> attendanceOnTheDayDTOS = attendanceService.buildAttendanceOnTheDay(attendanceGroupShiftItemDOS);
@ -154,6 +156,7 @@ public class AttendanceGroupServiceImpl implements AttendanceGroupService {
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());
@ -164,10 +167,12 @@ public class AttendanceGroupServiceImpl implements AttendanceGroupService {
attendancePunchRecordDO.setStatus(AttendanceOnTheDayDTO.PUNCH_STATUS_UN_PUNCH);
attendancePunchRecordDO.setFieldServiceFlag(Constants.FALSE);
attendancePunchRecordDO.setDayTime(time);
LocalDateTime shouldPunchTime = LocalDateTime.ofInstant(DateUtils.buildHHmmTime(attendanceOnTheDayDTO.getTime()).toInstant(), ZoneId.systemDefault());
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);
// TODO: 2024/5/24
}
}
}
@ -176,6 +181,11 @@ public class AttendanceGroupServiceImpl implements AttendanceGroupService {
}
}
@Override
public List<AttendanceGroupDO> getAll() {
return attendanceGroupMapper.selectList();
}
private Map<Long, Long> getAttendanceGroupShiftIdGroupByGroup(List<AttendanceGroupDO> attendanceGroupDOS, LocalDateTime localDateTime) {
Map<Long, Long> map = new HashMap<>();

View File

@ -1,28 +1,34 @@
package cn.iocoder.yudao.module.system.service.attendance.groupshift;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
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.groupshift.vo.AttendanceGroupShiftPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.groupshift.vo.AttendanceGroupShiftSaveReqVO;
import cn.iocoder.yudao.module.system.controller.admin.groupshift.vo.AttendanceGroupShiftVO;
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.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.scheduling.AttendanceSchedulingMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
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.GROUP_SHIFT_NOT_EXISTS;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
/**
* 考勤组班次 Service 实现类
@ -37,14 +43,23 @@ public class AttendanceGroupShiftServiceImpl implements AttendanceGroupShiftServ
private AttendanceGroupShiftMapper groupShiftMapper;
@Resource
private AttendanceGroupShiftItemMapper groupShiftItemMapper;
@Resource
private AttendanceFixedMapper attendanceFixedMapper;
@Resource
private AttendanceSchedulingMapper attendanceSchedulingMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public Long createGroupShift(AttendanceGroupShiftSaveReqVO createReqVO) {
// 插入
AttendanceGroupShiftDO groupShift = BeanUtils.toBean(createReqVO, AttendanceGroupShiftDO.class);
groupShiftMapper.insert(groupShift);
// -- 插入子表
List<AttendanceGroupShiftItemDO> items = BeanUtils.toBean(createReqVO.getItems(), AttendanceGroupShiftItemDO.class);
items = insertSpanDay(items);
long count = items.stream().filter(a -> a.getNextDayFlag() == 1).count();
groupShift.setNextDayFlag(count > 0 ? 1 : 0);
groupShiftMapper.insert(groupShift);
for (AttendanceGroupShiftItemDO item : items) {
item.setKqAttendanceGroupShiftId(groupShift.getId());
}
@ -53,21 +68,92 @@ public class AttendanceGroupShiftServiceImpl implements AttendanceGroupShiftServ
return groupShift.getId();
}
private static List<AttendanceGroupShiftItemDO> insertSpanDay(List<AttendanceGroupShiftItemDO> items) {
//先根据items中的level进行升序排序
items = items.stream().sorted(Comparator.comparingInt(AttendanceGroupShiftItemDO::getLevel)).collect(Collectors.toList());
AttendanceGroupShiftItemDO first = CollectionUtil.getFirst(items);
//获取明天时间
LocalDateTime tomorrow = LocalDateTimeUtil.offset(LocalDateTime.now(), 1, ChronoUnit.DAYS);
LocalDateTime today = LocalDateTime.now();
// -- 开始时间 - 这个肯定是当天的
Date todayStartTime = DateUtils.buildHHmmTime(first.getBeginTime(), today);
int nextDay = 0;
for (AttendanceGroupShiftItemDO item : items) {
Date beginTime = DateUtils.buildHHmmTime(item.getBeginTime(), nextDay == 0 ? today : tomorrow);
Date endTime = DateUtils.buildHHmmTime(item.getEndTime(), nextDay == 0 ? today : tomorrow);
int nextDayFlag = nextDay;
if (nextDay == 0) {
if (beginTime.getTime() < todayStartTime.getTime()) {
nextDayFlag = 2;
nextDay = 1;
}
if (endTime.getTime() < todayStartTime.getTime()) {
nextDayFlag = nextDayFlag == 0 ? 2 : 1;
nextDay = 1;
}
}
item.setNextDayFlag(nextDayFlag);
}
if (nextDay == 1) {
AttendanceGroupShiftItemDO last = CollectionUtil.getLast(items);
Date endTime = DateUtils.buildHHmmTime(last.getEndTime(), tomorrow);
//区间大于1天
Date deadlineTime = DateUtils.buildHHmmTime(first.getBeginTime(), tomorrow);
if (endTime.getTime() > deadlineTime.getTime()) {
throw exception(THE_ATTENDANCE_INTERVAL_SHALL_NOT_EXCEED24_HOURS);
}
}
return items;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateGroupShift(AttendanceGroupShiftSaveReqVO updateReqVO) {
// 校验存在
validateGroupShiftExists(updateReqVO.getId());
// 更新
AttendanceGroupShiftDO updateObj = BeanUtils.toBean(updateReqVO, AttendanceGroupShiftDO.class);
AttendanceGroupShiftDO attendanceGroupShiftDO = BeanUtils.toBean(updateReqVO, AttendanceGroupShiftDO.class);
// -- 插入子表
List<AttendanceGroupShiftItemDO> items = BeanUtils.toBean(updateReqVO.getItems(), AttendanceGroupShiftItemDO.class);
groupShiftItemMapper.updateBatch(items);
groupShiftMapper.updateById(updateObj);
items = insertSpanDay(items);
List<AttendanceGroupShiftItemDO> oldItems = groupShiftItemMapper.selectList(new LambdaQueryWrapper<AttendanceGroupShiftItemDO>()
.eq(AttendanceGroupShiftItemDO::getKqAttendanceGroupShiftId, updateReqVO.getId()));
if (CollectionUtil.isEmpty(oldItems)) {
groupShiftItemMapper.insertBatch(items);
return;
}
//新增
List<AttendanceGroupShiftItemDO> saveList = items.stream().filter(a -> a.getId() == null).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(saveList)) {
for (AttendanceGroupShiftItemDO attendanceGroupShiftItemDO : saveList) {
attendanceGroupShiftItemDO.setKqAttendanceGroupShiftId(updateReqVO.getId());
}
groupShiftItemMapper.insertBatch(saveList);
}
//修改
List<AttendanceGroupShiftItemDO> editList = items.stream().filter(a -> a.getId() != null).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(editList)) {
groupShiftItemMapper.updateBatch(editList);
}
//删除
// -- 需要删除的 -
List<Long> oldIds = oldItems.stream().map(AttendanceGroupShiftItemDO::getId).collect(Collectors.toList());
List<Long> newIds = editList.stream().map(AttendanceGroupShiftItemDO::getId).collect(Collectors.toList());
List<Long> delIds = new ArrayList<>(CollectionUtil.subtract(oldIds, newIds));
if (!delIds.isEmpty()) {
groupShiftItemMapper.deleteBatchIds(delIds);
}
groupShiftMapper.updateById(attendanceGroupShiftDO);
}
@Override
public void deleteGroupShift(Long id) {
//查看当前考勤组班次是否在使用 如果是的话 则不允许删除
Long fixedNum = attendanceFixedMapper.selectCount(new LambdaQueryWrapper<AttendanceFixedDO>().eq(AttendanceFixedDO::getAttendanceGroupShiftId, id));
Long schedulingNum = attendanceSchedulingMapper.selectCount(new LambdaQueryWrapper<AttendanceSchedulingDO>().eq(AttendanceSchedulingDO::getAttendanceGroupShiftId, id));
if (fixedNum + schedulingNum > 0) {
throw exception(GROUP_SHIFT_IN_USE);
}
// 校验存在
validateGroupShiftExists(id);
// 删除

View File

@ -50,6 +50,9 @@ public class AttendanceOnTheDayDTO {
@Schema(description = "是否外勤 0否 1是", example = "1")
private Integer fieldServiceFlag;
@Schema(description = "是否跨天 0否 1是", example = "1")
private Integer nextDayFlag;
@Schema(description = "用户打卡地点")
private String punchAddress;

View File

@ -116,7 +116,7 @@ public class AttendanceSchedulingServiceImpl implements AttendanceSchedulingServ
@Override
public AttendanceSchedulingDO getSchedulingByIndexDay(Long attendanceGroupId, Integer indexDay) {
List<AttendanceSchedulingDO> list = schedulingMapper.selectList(new LambdaQueryWrapper<AttendanceSchedulingDO>()
.eq(AttendanceSchedulingDO::getAttendanceGroupShiftId, attendanceGroupId)
.eq(AttendanceSchedulingDO::getAttendanceGroupId, attendanceGroupId)
.eq(AttendanceSchedulingDO::getIndexDay, indexDay));
if (!list.isEmpty()) {
return list.get(0);

View File

@ -154,6 +154,14 @@ public interface AdminUserService {
*/
List<UserRespVO> getUserByDeptIds(Collection<Long> deptIds);
/**
* 获取部门下人员过滤掉考勤组内有的
*
* @param deptIds
* @return
*/
List<UserRespVO> selectByDeptIdsFilterGroupUser(Collection<Long> deptIds);
/**
* 获得指定部门的用户数组除去当前登录者用户
*
@ -251,13 +259,15 @@ public interface AdminUserService {
/**
* 获取用户的签名图片地址
*
* @param userId
* @return
*/
String getSignImgPath(Long userId) ;
String getSignImgPath(Long userId);
/**
* 获取 岗位为总监或副总裁 以及部门层级为2级或3级的负责人的用户编号
*
* @return 用户编号
*/
List<Long> getUserByBoss();

View File

@ -185,17 +185,17 @@ public class AdminUserServiceImpl implements AdminUserService {
throw exception(USER_NOT_EXISTS);
}
byte[] content = IoUtil.readBytes(avatarFile) ;
String avatar = "" ;
if(StringUtil.isEmpty(user.getAvatar())) {
byte[] content = IoUtil.readBytes(avatarFile);
String avatar = "";
if (StringUtil.isEmpty(user.getAvatar())) {
//没有头像 新增
// 存储文件
// BusinessFile file = new BusinessFile().setBusinessType(3L).setContent(content).setName(name);
avatar = fileApi.createBusinessFile(3L, name, content) ;
}else {
avatar = fileApi.createBusinessFile(3L, name, content);
} else {
//有头像 修改
//变更infra_file_content的content字段内容
avatar = fileApi.updateBusinessFileContent(user.getAvatar(), 3L, name, content) ;
avatar = fileApi.updateBusinessFileContent(user.getAvatar(), 3L, name, content);
}
user.setAvatar(avatar);
userMapper.updateById(user);
@ -273,6 +273,14 @@ public class AdminUserServiceImpl implements AdminUserService {
return userMapper.selectByDeptIds(deptIds);
}
@Override
public List<UserRespVO> selectByDeptIdsFilterGroupUser(Collection<Long> deptIds) {
if (CollUtil.isEmpty(deptIds)) {
return Collections.emptyList();
}
return userMapper.selectByDeptIdsFilterGroupUser(deptIds);
}
@Override
public List<AdminUserDO> getListUserByDeptId(Long deptId) {
@ -546,7 +554,7 @@ public class AdminUserServiceImpl implements AdminUserService {
public String getSignImgPath(Long userId) {
//2L 用户签名
String path = fileApi.getUserSignImgPath(userId).getData();
return path ;
return path;
}
@Override

View File

@ -41,4 +41,19 @@
)
AND b.leader_user_id = a.id)
</select>
<select id="selectByDeptIdsFilterGroupUser"
resultType="cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserRespVO">
select a.*, b.name deptName FROM system_users a, system_dept b
where
a.dept_id = b.id
and a.status = 0
and a.user_type = 1
and a.dept_id in
<foreach collection="list" item="deptIds" open="(" close=")" separator=",">
#{deptIds}
</foreach>
<if test="groupId != null">
and not exists(select id from kq_attendance_group_user where user_id = a.id)
</if>
</select>
</mapper>