请假考勤调整

This commit is contained in:
aikai 2024-07-23 17:14:00 +08:00
parent 24d3508ea8
commit 2ae403942e
21 changed files with 538 additions and 32 deletions

View File

@ -0,0 +1,27 @@
package cn.iocoder.yudao.framework.common.pojo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class BpmOALeaveDTO {
/**
* 请假表单主键
*/
private Long id;
/**
* 申请人的用户编号
*
* 关联 AdminUserDO id 属性
*/
private Long userId;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
}

View File

@ -8,6 +8,7 @@ import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.Constants;
import java.text.SimpleDateFormat;
import java.time.*;
@ -257,6 +258,25 @@ public class DateUtils {
return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());
}
/**
* 获取两个时间区间 - 获取两个时间区间的所有日期
*
* @param beginTime
* @param endTime
* @return
*/
public static List<String> betweenDayList(LocalDateTime beginTime, LocalDateTime endTime) {
List<String> list = new ArrayList<>();
long num = LocalDateTimeUtil.between(beginTime, endTime, ChronoUnit.DAYS);
for (int i = 0; i <= num; i++) {
LocalDateTime dateTime = LocalDateTimeUtil.offset(beginTime, i, ChronoUnit.DAYS);
list.add(dateTime.format(Constants.REPO_DATE_FORMAT));
}
return list;
}
/**
* 获取两个时间区间 - 获取两个时间区间的所有日期
*
@ -357,6 +377,7 @@ public class DateUtils {
/**
* 将1-7的数字转换为对应的中文星期表示
*
* @param dayOfWeek 数字形式的星期几1-7分别代表星期一到星期日
* @return 中文表示的星期几
*/

View File

@ -20,6 +20,7 @@ public interface ErrorCodeConstants {
ErrorCode OA_DEPART_BM_POST_NOT_EXISTS = new ErrorCode(1_009_001_005, "部门的部门经理不存在");
ErrorCode OA_HR_POST_NOT_EXISTS = new ErrorCode(1_009_001_006, "HR岗位未设置");
ErrorCode OA_DAY_LEAVE_ERROR = new ErrorCode(1_009_001_007, "请假天数必须>=1");
ErrorCode FAILED_TO_APPLY_FOR_LEAVE = new ErrorCode(1_009_001_008, "请假失败");
ErrorCode OA_REIMBURSEMENT_NOT_EXISTS = new ErrorCode(1_009_001_100, "报销申请不存在");
ErrorCode OA_EVECTION_NOT_EXISTS = new ErrorCode(1_009_001_101, "出差申请不存在");

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.framework.rpc.config;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.system.api.attendance.AttendanceApi;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.PostApi;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
@ -15,8 +16,8 @@ import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Configuration;
@Configuration(proxyBeanMethods = false)
@EnableFeignClients(clients = {FileApi.class,RoleApi.class, DeptApi.class, PostApi.class, AdminUserApi.class, SmsSendApi.class, DictDataApi.class, NotifyMessageSendApi.class,
SubscribeMessageSendApi.class, SocialClientApi.class, UsersExtApi.class
@EnableFeignClients(clients = {FileApi.class, RoleApi.class, DeptApi.class, PostApi.class, AdminUserApi.class, SmsSendApi.class, DictDataApi.class, NotifyMessageSendApi.class,
SubscribeMessageSendApi.class, SocialClientApi.class, UsersExtApi.class, AttendanceApi.class
})
public class RpcConfiguration {
}

View File

@ -1,6 +1,10 @@
package cn.iocoder.yudao.module.bpm.service.oa;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.common.pojo.BpmOALeaveDTO;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.api.oa.vo.BpmOALeaveRpcVO;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
@ -12,17 +16,21 @@ import cn.iocoder.yudao.module.bpm.convert.oa.BpmOALeaveConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOALeaveDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOALeaveMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import cn.iocoder.yudao.module.system.api.attendance.AttendanceApi;
import cn.iocoder.yudao.module.system.api.attendance.dto.AttendancePunchRecordDTO;
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.util.HashMap;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.FAILED_TO_APPLY_FOR_LEAVE;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.OA_LEAVE_NOT_EXISTS;
/**
@ -46,6 +54,8 @@ public class BpmOALeaveServiceImpl extends BpmOABaseService implements BpmOALeav
private BpmProcessInstanceApi processInstanceApi;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private AttendanceApi attendanceApi;
@Override
@Transactional(rollbackFor = Exception.class)
@ -77,6 +87,7 @@ public class BpmOALeaveServiceImpl extends BpmOABaseService implements BpmOALeav
@Override
@Transactional(rollbackFor = Exception.class)
public void updateLeaveResult(Long id, Integer result) {
BpmOALeaveDO bpmOALeaveDO = validateLeaveExists(id);
leaveMapper.updateById(new BpmOALeaveDO().setId(id).setResult(result));
@ -84,17 +95,28 @@ public class BpmOALeaveServiceImpl extends BpmOABaseService implements BpmOALeav
// -- 如果是的话 先插入到redis中 - (事前请假)
// -- 如果不是 则查询相应的考勤 修改考勤状态 事后请假 - 还需要判断结束时间是否
//
// if (result.equals(BpmProcessInstanceResultEnum.APPROVE.getResult())) {
// if (LocalDateTimeUtil.between(bpmOALeaveDO.getStartTime(), LocalDateTimeUtil.now()).toDays() > 0) {
// // -- 插入到redis中
// LeaveVO leaveVO = new LeaveVO();
// BeanUtil.copyProperties(bpmOALeaveDO, leaveVO);
// stringRedisTemplate.opsForHash().put("leave", bpmOALeaveDO.getUserId().toString(), JSONUtil.toJsonStr(leaveVO));
// } else {
// // -- 查询相应的考勤 修改考勤状态
//
// }
// }
LocalDateTime now = LocalDateTimeUtil.now();
if (result.equals(BpmProcessInstanceResultEnum.APPROVE.getResult())) {
// 事后请假修改考勤 = 考勤的预设已经生成过了 - 并且已经在表里面存在了 - 所以要找到表中的数据 - 修改考勤状态
CommonResult commonResult = attendanceApi.askingForLeaveAfterwardsToModifyAttendance(new AttendancePunchRecordDTO()
.setUserId(bpmOALeaveDO.getUserId())
.setStartTime(bpmOALeaveDO.getStartTime())
.setEndTime(bpmOALeaveDO.getEndTime())
.setLeaveId(id)
);
if (!commonResult.isSuccess()) {
throw exception(FAILED_TO_APPLY_FOR_LEAVE);
}
if (now.isBefore(bpmOALeaveDO.getEndTime())) {
// 事前请假 = 考勤预设可能还没有生成 - 因为可能请假好几天 - 所以这里处理就比较麻烦点 - 先看下考勤表里面有没有在这个区间的考勤 - 如果有的话先修改考勤状态 -
// 然后将数据先存入redis - 在设置考勤预设的时候 就去redis 中查询是否有请假 - 有的话预设的时候就预设进去
BpmOALeaveDTO dto = new BpmOALeaveDTO();
BeanUtil.copyProperties(bpmOALeaveDO, dto);
String key = "leave" + "_" + bpmOALeaveDO.getUserId().toString();
stringRedisTemplate.opsForHash().put(key, id.toString(), JSONUtil.toJsonStr(dto));
// -- 将请假put到redis的map中 -
}
}
}
private BpmOALeaveDO validateLeaveExists(Long id) {

View File

@ -0,0 +1,26 @@
package cn.iocoder.yudao.module.system.api.attendance;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.api.attendance.dto.AttendancePunchRecordDTO;
import cn.iocoder.yudao.module.system.api.attendance.vo.AttendancePunchRecordVO;
import cn.iocoder.yudao.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@Tag(name = "RPC 服务 - 考勤")
public interface AttendanceApi {
String PREFIX = ApiConstants.PREFIX + "/attendance";
@PostMapping(PREFIX + "/askingForLeaveAfterwardsToModifyAttendance")
@Operation(summary = "获取考勤记录")
CommonResult askingForLeaveAfterwardsToModifyAttendance(@RequestBody AttendancePunchRecordDTO attendancePunchRecordDTO);
}

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.system.api.attendance.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* 用户打卡记录 DTO
*
* @author 艾楷
*/
@Data
@Accessors(chain = true)
public class AttendancePunchRecordDTO {
/**
* 用户id
*/
private Long userId;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
/**
* 请假id 对应bpm_oa_leave表id
*/
private Long leaveId;
}

View File

@ -0,0 +1,138 @@
package cn.iocoder.yudao.module.system.api.attendance.vo;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 用户打卡记录 DO
*
* @author 艾楷
*/
@Data
public class AttendancePunchRecordVO {
/**
* 编号
*/
private Long id;
/**
* 考勤组管理员id
*/
private Long userId;
/**
* 部门id
*/
private Long deptId;
/**
* 考勤组id
*/
private Long attendanceGroupId;
/**
* 考勤组名称
*/
private String attendanceGroupName;
/**
* 班次id
*/
private Long attendanceGroupShiftId;
/**
* 班次名称
*/
private String attendanceGroupShiftName;
/**
* 班次子表id
*/
private Long attendanceGroupShiftItemId;
/**
* 考勤类型 1固定班制 2排班制
*/
private Integer type;
/**
* 打卡类型 1考勤机 2小程序范围打卡
*/
private Integer punchType;
/**
* 上下班类型 0上班 1下班
*/
private Integer workType;
/**
* 级别 1到
*/
private Integer level;
/**
* 打卡状态 0正常 1迟到 2早退 3缺卡 4未打卡(还没到打卡时间) 5补卡 6请假
*/
private Integer status;
/**
* 明天的打卡 0否 1是 (业务需要 先把明天的打卡统计出来)
*/
private Integer nextDayFlag;
/**
* 是否外勤 0否 1是
*/
private Integer fieldServiceFlag;
/**
* 日期yyyy-MM-dd格式 (归属于哪一天)
*/
private String dayTime;
/**
* 日期yyyy-MM-dd格式 (实际是哪一天)
*/
private String actualDayTime;
/**
* 打卡时间
*/
private LocalDateTime punchTime;
/**
* 应打卡时间
*/
private LocalDateTime shouldPunchTime;
/**
* 最晚打卡时间(超过即为缺卡)
*/
private LocalDateTime latestPunchTime;
/**
* 打卡备注
*/
private String remark;
/**
* 图片
*/
private String image;
/**
* 用户打卡地点
*/
private String punchAddress;
/**
* 迟到时长时间戳
*/
private Long lateTime;
/**
* 早退时长时间戳
*/
private Long leaveEarlyTime;
/**
* 加班时长时间戳
*/
private Long workOvertimeTime;
/**
* 是否已提醒 0否 1是
*/
private Integer remindFlag;
/**
* 请假json对象
*/
private String leaveJson;
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.system.api.attendance;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.api.attendance.dto.AttendancePunchRecordDTO;
import cn.iocoder.yudao.module.system.service.attendance.punchrecord.AttendancePunchRecordService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController // 提供 RESTful API 接口 Feign 调用
@Validated
public class AttendanceApiImpl implements AttendanceApi {
@Resource
private AttendancePunchRecordService attendancePunchRecordService;
@Override
public CommonResult askingForLeaveAfterwardsToModifyAttendance(AttendancePunchRecordDTO attendancePunchRecordDTO) {
attendancePunchRecordService.askingForLeaveAfterwardsToModifyAttendance(attendancePunchRecordDTO);
return CommonResult.success("ok");
}
}

View File

@ -25,6 +25,8 @@ public class AttendanceStatisticsVO {
private List<AttendancePunchStatisticsVO> fieldServiceNumber;
@Schema(description = "休息日")
private List<AttendancePunchStatisticsVO> restDays;
@Schema(description = "请假次数")
private List<AttendancePunchStatisticsVO> leaveNumber;
@Schema(description = "头部次数")
private CalculateNum calculateNum;
}

View File

@ -9,7 +9,7 @@ import java.util.List;
@Data
@Accessors(chain = true)
public class AttendanceStatusByDayVO {
@Schema(description = "状态 0正常 1异常(迟到/早退/缺卡)")
@Schema(description = "状态 0正常 1异常(迟到/早退/缺卡/请假/补卡)")
private Integer status;
@Schema(description = "是否有提交过 请假/加班/出差/外出/补卡 申请 0否 1是")
private Integer submitStatus = 0;

View File

@ -27,6 +27,8 @@ public class CalculateNum {
String totalEarlyDeparturesTimeStr;
@Schema(description = "缺卡总次数")
int totalMissingCardsNumber = 0;
@Schema(description = "请假总次数")
int totalLeaveNumber = 0;
@Schema(description = "上班缺卡总次数")
int totalUpMissingCardsNumber = 0;
@Schema(description = "下班缺卡总次数")

View File

@ -30,6 +30,9 @@ public class TeamAttendanceStatisticsByCycleVO {
@Schema(description = "外勤")
private List<TeamAttendancePunchStatisticsVO> fieldServiceList;
@Schema(description = "请假")
private List<TeamAttendancePunchStatisticsVO> leaveList;
@Schema(description = "顶部")
private TeamAttendanceStatisticsNumVO teamAttendanceStatisticsNumVO;
@ -52,6 +55,9 @@ public class TeamAttendanceStatisticsByCycleVO {
@Schema(description = "缺卡次数")
private Integer missingCardNum;
@Schema(description = "请假次数")
private Integer leaveNum;
@Schema(description = "旷工次数")
private Integer absenteeismNum;

View File

@ -19,4 +19,6 @@ public class TeamAttendanceStatisticsByDayVO {
private Integer leaveEarlyNum;
@Schema(description = "外勤数量")
private Integer fieldworkNum;
@Schema(description = "请假数量")
private Integer leaveNum;
}

View File

@ -77,7 +77,7 @@ public class AttendancePunchRecordDO extends BaseDO {
private Integer level;
/**
* 打卡状态 0正常 1迟到 2早退 3缺卡 4未打卡(还没到打卡时间) 5补卡
* 打卡状态 0正常 1迟到 2早退 3缺卡 4未打卡(还没到打卡时间) 5补卡 6请假
*/
private Integer status;
@ -91,9 +91,14 @@ public class AttendancePunchRecordDO extends BaseDO {
*/
private Integer fieldServiceFlag;
/**
* 日期yyyy-MM-dd格式
* 日期yyyy-MM-dd格式 (归属于哪一天)
*/
private String dayTime;
/**
* 日期yyyy-MM-dd格式 (实际是哪一天)
*/
private String actualDayTime;
/**
* 打卡时间
*/
@ -140,6 +145,11 @@ public class AttendancePunchRecordDO extends BaseDO {
*/
private Integer remindFlag;
/**
* 请假单id
*/
private Long leaveId;
@TableField(exist = false)
private String deptName;
}
}

View File

@ -3,9 +3,10 @@ package cn.iocoder.yudao.module.system.dal.mysql.attendance.punchrecord;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.api.attendance.dto.AttendancePunchRecordDTO;
import cn.iocoder.yudao.module.system.api.attendance.vo.AttendancePunchRecordVO;
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.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.service.attendance.punch.dto.AttendanceOnTheDayDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@ -47,4 +48,4 @@ public interface AttendancePunchRecordMapper extends BaseMapperX<AttendancePunch
* @return
*/
List<AttendancePunchRecordDO> statistics(@Param("userList") List<Long> userList, @Param("dateList") List<String> dateList);
}
}

View File

@ -446,7 +446,12 @@ public class AttendanceServiceImpl implements AttendanceService {
Map<String, List<AttendancePunchRecordDO>> collect = list.stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getDayTime));
List<Integer> statusList = Arrays.asList(AttendanceOnTheDayDTO.PUNCH_STATUS_LATE, AttendanceOnTheDayDTO.PUNCH_STATUS_LEAVE_EARLY, AttendanceOnTheDayDTO.PUNCH_STATUS_MISS);
List<Integer> statusList = Arrays.asList(AttendanceOnTheDayDTO.PUNCH_STATUS_LATE,
AttendanceOnTheDayDTO.PUNCH_STATUS_LEAVE_EARLY,
AttendanceOnTheDayDTO.PUNCH_STATUS_MISS,
AttendanceOnTheDayDTO.REPLACEMENT_CARD,
AttendanceOnTheDayDTO.ASK_FOR_LEAVE
);
for (Map.Entry<String, List<AttendancePunchRecordDO>> entry : collect.entrySet()) {
AttendanceStatusByDayVO attendanceStatusByDayVO = new AttendanceStatusByDayVO();
int status = 0;
@ -504,6 +509,7 @@ public class AttendanceServiceImpl implements AttendanceService {
List<AttendancePunchStatisticsVO> beLateNumber = new ArrayList<>();
List<AttendancePunchStatisticsVO> earlyDeparturesNumber = new ArrayList<>();
List<AttendancePunchStatisticsVO> missingCardsNumber = new ArrayList<>();
List<AttendancePunchStatisticsVO> leaveNumber = new ArrayList<>();
List<AttendancePunchStatisticsVO> minerDays = new ArrayList<>();
List<AttendancePunchStatisticsVO> fieldServiceNumber = new ArrayList<>();
CalculateNum calculateNum = new CalculateNum();
@ -533,6 +539,10 @@ public class AttendanceServiceImpl implements AttendanceService {
List<AttendancePunchStatisticsVO> missingCardsList = this.calculateMissingCardsList(day, weekChinese, workMap, calculateNum);
missingCardsNumber.addAll(missingCardsList);
// -- 请假计算
List<AttendancePunchStatisticsVO> leaveList = this.calculateLeaveList(day, weekChinese, workMap, calculateNum);
leaveNumber.addAll(leaveList);
// -- 旷工计算
AttendancePunchStatisticsVO miner = this.calculateMiner(day, weekChinese, entry.getValue(), calculateNum);
if (miner != null) {
@ -553,6 +563,7 @@ public class AttendanceServiceImpl implements AttendanceService {
vo.setBeLateNumber(beLateNumber);
vo.setEarlyDeparturesNumber(earlyDeparturesNumber);
vo.setMissingCardsNumber(missingCardsNumber);
vo.setLeaveNumber(leaveNumber);
vo.setMinerDays(minerDays);
vo.setFieldServiceNumber(fieldServiceNumber);
vo.setRestDays(restDays);
@ -560,6 +571,80 @@ public class AttendanceServiceImpl implements AttendanceService {
return vo;
}
/**
* 请假计算
*
* @param workMap
* @return
*/
private String calculateLeaveList(Map<Long, List<AttendancePunchRecordDO>> workMap) {
int todayLeaveNumber = 0;
for (Map.Entry<Long, List<AttendancePunchRecordDO>> workEntry : workMap.entrySet()) {
if (CollectionUtil.isNotEmpty(workEntry.getValue())) {
// -- 请假计算
List<AttendancePunchRecordDO> leaveList = workEntry.getValue().stream().filter(a -> AttendanceOnTheDayDTO.ASK_FOR_LEAVE.equals(a.getStatus())).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(leaveList)) {
todayLeaveNumber += leaveList.size();
}
}
}
return String.valueOf(todayLeaveNumber);
}
/**
* 请假计算
*
* @param workMap
* @param calculateNum
* @return
*/
private void calculateLeaveList(Map<Long, List<AttendancePunchRecordDO>> workMap, CalculateNum calculateNum) {
int todayLeaveNumber = 0;
for (Map.Entry<Long, List<AttendancePunchRecordDO>> workEntry : workMap.entrySet()) {
if (CollectionUtil.isNotEmpty(workEntry.getValue())) {
// -- 请假计算
List<AttendancePunchRecordDO> leaveList = workEntry.getValue().stream().filter(a -> AttendanceOnTheDayDTO.ASK_FOR_LEAVE.equals(a.getStatus())).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(leaveList)) {
todayLeaveNumber += leaveList.size();
}
}
}
calculateNum.setTotalLeaveNumber(calculateNum.getTotalLeaveNumber() + todayLeaveNumber);
}
/**
* 请假计算
*
* @param day
* @param weekChinese
* @param workMap
* @param calculateNum
* @return
*/
private List<AttendancePunchStatisticsVO> calculateLeaveList(String day, String weekChinese, Map<Long, List<AttendancePunchRecordDO>> workMap, CalculateNum calculateNum) {
List<AttendancePunchStatisticsVO> list = new ArrayList<>();
int totalLeaveNumber = 0;
for (Map.Entry<Long, List<AttendancePunchRecordDO>> workEntry : workMap.entrySet()) {
if (CollectionUtil.isNotEmpty(workEntry.getValue())) {
// -- 缺卡计算
List<AttendancePunchRecordDO> missingCardsList = workEntry.getValue().stream().filter(a -> AttendanceOnTheDayDTO.ASK_FOR_LEAVE.equals(a.getStatus())).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(missingCardsList)) {
totalLeaveNumber += missingCardsList.size();
for (AttendancePunchRecordDO attendancePunchRecordDO : missingCardsList) {
AttendancePunchStatisticsVO earlyDepartures = new AttendancePunchStatisticsVO();
earlyDepartures.setDay(day);
earlyDepartures.setWeek(weekChinese);
earlyDepartures.setShouldPunchTime(attendancePunchRecordDO.getShouldPunchTime().format(DateTimeFormatter.ofPattern("HH:mm")));
list.add(earlyDepartures);
}
}
}
}
calculateNum.setTotalLeaveNumber(calculateNum.getTotalLeaveNumber() + totalLeaveNumber);
return list;
}
@Override
public Map<String, TeamAttendanceStatisticsByDayVO> teamStatisticsByDay(TeamAttendanceStatisticsByDayDTO dto) {
Map<String, TeamAttendanceStatisticsByDayVO> map = new HashMap<>();
@ -587,6 +672,7 @@ public class AttendanceServiceImpl implements AttendanceService {
int lateNum = 0;
int leaveEarlyNum = 0;
int fieldworkNum = 0;
int leaveNum = 0;
for (AttendancePunchRecordDO attendancePunchRecordDO : entry.getValue()) {
if (AttendanceOnTheDayDTO.PUNCH_STATUS_MISS.equals(attendancePunchRecordDO.getStatus())) {
punchStatusMissNum++;
@ -600,6 +686,9 @@ public class AttendanceServiceImpl implements AttendanceService {
if (Constants.TRUE.equals(attendancePunchRecordDO.getFieldServiceFlag())) {
fieldworkNum++;
}
if (AttendanceOnTheDayDTO.ASK_FOR_LEAVE.equals(attendancePunchRecordDO.getStatus())) {
leaveNum++;
}
}
TeamAttendanceStatisticsByDayVO vo = new TeamAttendanceStatisticsByDayVO();
vo.setAnswerNum(userMap.keySet().size());
@ -608,6 +697,7 @@ public class AttendanceServiceImpl implements AttendanceService {
vo.setLateNum(lateNum);
vo.setLeaveEarlyNum(leaveEarlyNum);
vo.setFieldworkNum(fieldworkNum);
vo.setLeaveNum(leaveNum);
map.put(entry.getKey(), vo);
}
return map;
@ -661,6 +751,8 @@ public class AttendanceServiceImpl implements AttendanceService {
List<TeamAttendancePunchStatisticsVO> absenteeismList = new ArrayList<>();
//外勤
List<TeamAttendancePunchStatisticsVO> fieldServiceList = new ArrayList<>();
//请假
List<TeamAttendancePunchStatisticsVO> leaveList = new ArrayList<>();
Map<Long, List<AttendancePunchRecordDO>> userPunchMap = list.stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getUserId));
// -- 根据用户分组
@ -690,6 +782,8 @@ public class AttendanceServiceImpl implements AttendanceService {
this.calculateEarlyDepartures(workMap, calculateNum);
// -- 缺卡
this.calculateMissingCardsList(workMap, calculateNum);
// -- 请假
this.calculateLeaveList(workMap, calculateNum);
// -- 旷工
this.calculateMiner(dayEntry.getValue(), calculateNum);
// -- 外勤
@ -733,6 +827,14 @@ public class AttendanceServiceImpl implements AttendanceService {
BeanUtil.copyProperties(averageWorkingHourVO, item);
fieldServiceList.add(item.setTop(calculateNum.getTotalFieldServiceNumber() + "").setDown(""));
}
// --- 请假漏卡
if (calculateNum.getTotalLeaveNumber() > 0) {
TeamAttendancePunchStatisticsVO item = new TeamAttendancePunchStatisticsVO();
BeanUtil.copyProperties(averageWorkingHourVO, item);
leaveList.add(item.setTop(calculateNum.getTotalLeaveNumber() + "次请假漏卡").setDown(""));
}
}
vo.setAverageWorkingHoursList(averageWorkingHours);
@ -741,6 +843,8 @@ public class AttendanceServiceImpl implements AttendanceService {
vo.setMissingCardList(missingCardList);
vo.setAbsenteeismList(absenteeismList);
vo.setFieldServiceList(fieldServiceList);
vo.setLeaveList(leaveList);
int sum = averageWorkingHours.stream().mapToInt(a -> a.getCalculateNum().getTotalAttendanceDays()).sum();
TeamAttendanceStatisticsByCycleVO.TeamAttendanceStatisticsNumVO teamAttendanceStatisticsByCycleVO = new TeamAttendanceStatisticsByCycleVO.TeamAttendanceStatisticsNumVO();
teamAttendanceStatisticsByCycleVO.setAverageWorkingHours(
@ -750,6 +854,7 @@ public class AttendanceServiceImpl implements AttendanceService {
.setBeLateNum(beLateList.stream().mapToInt(a -> a.getCalculateNum().getTotalLateArrivalsNumber()).sum())
.setLeaveEarlyNum(leaveEarlyList.stream().mapToInt(a -> a.getCalculateNum().getTotalEarlyDeparturesNumber()).sum())
.setMissingCardNum(missingCardList.stream().mapToInt(a -> a.getCalculateNum().getTotalMissingCardsNumber()).sum())
.setLeaveNum(leaveList.stream().mapToInt(a -> a.getCalculateNum().getTotalLeaveNumber()).sum())
.setAbsenteeismNum(absenteeismList.stream().mapToInt(a -> a.getCalculateNum().getTotalMinerDays()).sum())
.setFieldServiceNum(fieldServiceList.stream().mapToInt(a -> a.getCalculateNum().getTotalFieldServiceNumber()).sum())
);
@ -913,6 +1018,10 @@ public class AttendanceServiceImpl implements AttendanceService {
//早退时长
String leaveEarly = this.calculateEarlyDepartures(workMap);
row.add(leaveEarly);
//请假漏卡次数
String leaveList = this.calculateLeaveList(workMap);
row.add(leaveList);
Map<Integer, Integer> missingCardsMap = this.calculateCommuteMissingCardsList(workMap);
row.add(missingCardsMap.get(Constants.ZERO).toString());
row.add(missingCardsMap.get(Constants.ONE).toString());
@ -926,7 +1035,8 @@ public class AttendanceServiceImpl implements AttendanceService {
// 这里 需要指定写用哪个class去写然后写到第一个sheet名字为模板 然后文件流会自动关闭
try {
EasyExcel.write(response.getOutputStream())
// EasyExcel.write(response.getOutputStream())
EasyExcel.write("/Users/aikai/Downloads/" + System.currentTimeMillis() + "考勤统计按日导出.xls")
.head(generateDailyHead(headTitle, detailedHead, maxSize))
.autoCloseStream(false)
.excelType(ExcelTypeEnum.XLS)
@ -934,7 +1044,7 @@ public class AttendanceServiceImpl implements AttendanceService {
.sheet("考勤统计按日导出")
.doWrite(data);
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("考勤统计", StandardCharsets.UTF_8.name()));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
// response.setContentType("application/vnd.ms-excel;charset=UTF-8");
} catch (IOException e) {
throw new RuntimeException(e);
@ -980,6 +1090,8 @@ public class AttendanceServiceImpl implements AttendanceService {
return "未打卡";
} else if (status == 5) {
return "补卡";
} else if (status == 6) {
return "请假";
}
return "";
}
@ -1035,6 +1147,8 @@ public class AttendanceServiceImpl implements AttendanceService {
this.calculateCommuteMissingCardsList(workMap, calculateNum);
// -- 旷工
this.calculateMiner(dayEntry.getValue(), calculateNum);
// -- 请假
this.calculateLeaveList(workMap, calculateNum);
}
//休息天数
this.calculateRestDay(dateList, dayMap.keySet().stream().distinct().collect(Collectors.toList()), calculateNum);
@ -1058,6 +1172,7 @@ public class AttendanceServiceImpl implements AttendanceService {
row.add(String.valueOf(calculateNum.getTotalEarlyDeparturesNumber()));
row.add(String.valueOf(calculateNum.getTotalEarlyDeparturesTimeStr()));
row.add(String.valueOf(calculateNum.getTotalLeaveNumber()));
row.add(String.valueOf(calculateNum.getTotalUpMissingCardsNumber()));
row.add(String.valueOf(calculateNum.getTotalDownMissingCardsNumber()));
row.add(String.valueOf(calculateNum.getTotalMinerDays()));
@ -1067,7 +1182,8 @@ public class AttendanceServiceImpl implements AttendanceService {
}
try {
EasyExcel.write(response.getOutputStream())
// EasyExcel.write(response.getOutputStream())
EasyExcel.write("/Users/aikai/Downloads/" + System.currentTimeMillis() + "考勤统计按日导出.xls")
.head(generateHead(headTitle, detailedHead))
.autoCloseStream(false)
.excelType(ExcelTypeEnum.XLS)
@ -1075,7 +1191,7 @@ public class AttendanceServiceImpl implements AttendanceService {
.sheet("考勤统计按月导出")
.doWrite(data);
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("考勤统计", StandardCharsets.UTF_8.name()));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
// response.setContentType("application/vnd.ms-excel;charset=UTF-8");
} catch (IOException e) {
throw new RuntimeException(e);
@ -1096,6 +1212,7 @@ public class AttendanceServiceImpl implements AttendanceService {
head.add(Arrays.asList(headTitle, detailedHead, "迟到时长", "迟到时长"));
head.add(Arrays.asList(headTitle, detailedHead, "早退次数", "早退次数"));
head.add(Arrays.asList(headTitle, detailedHead, "早退时长", "早退时长"));
head.add(Arrays.asList(headTitle, detailedHead, "请假漏卡次数", "请假漏卡次数"));
head.add(Arrays.asList(headTitle, detailedHead, "上班缺卡次数", "上班缺卡次数"));
head.add(Arrays.asList(headTitle, detailedHead, "下班缺卡次数", "下班缺卡次数"));
head.add(Arrays.asList(headTitle, detailedHead, "旷工天数", "旷工天数"));
@ -1140,6 +1257,7 @@ public class AttendanceServiceImpl implements AttendanceService {
head.add(Arrays.asList(headTitle, detailedHead, "迟到时长", "迟到时长"));
// head.add(Arrays.asList(headTitle, detailedHead, "早退次数", "早退次数"));
head.add(Arrays.asList(headTitle, detailedHead, "早退时长", "早退时长"));
head.add(Arrays.asList(headTitle, detailedHead, "请假漏卡次数", "请假漏卡次数"));
head.add(Arrays.asList(headTitle, detailedHead, "上班缺卡次数", "上班缺卡次数"));
head.add(Arrays.asList(headTitle, detailedHead, "下班缺卡次数", "下班缺卡次数"));
// head.add(Arrays.asList(headTitle, detailedHead, "旷工天数", "旷工天数"));
@ -1319,7 +1437,7 @@ public class AttendanceServiceImpl implements AttendanceService {
}
/**
* 迟到次数
* 缺卡次数 - 上下班区分开
*
* @param workMap
*/

View File

@ -9,13 +9,15 @@ import java.time.LocalDateTime;
public class AttendanceOnTheDayDTO {
/**
* 打卡状态 0正常 1迟到 2早退 3缺卡 4未打卡(还没到打卡时间)
* 打卡状态 0正常 1迟到 2早退 3缺卡 4未打卡(还没到打卡时间) 5补卡 6请假
*/
public static final Integer PUNCH_STATUS_NORMAL = 0;
public static final Integer PUNCH_STATUS_LATE = 1;
public static final Integer PUNCH_STATUS_LEAVE_EARLY = 2;
public static final Integer PUNCH_STATUS_MISS = 3;
public static final Integer PUNCH_STATUS_UN_PUNCH = 4;
public static final Integer REPLACEMENT_CARD = 5;
public static final Integer ASK_FOR_LEAVE = 6;
@Schema(description = "子表id")
private Long id;
@ -44,7 +46,7 @@ public class AttendanceOnTheDayDTO {
@Schema(description = "后打卡时间(分钟) 默认 120分钟")
private Integer afterPunchTime;
@Schema(description = "打卡状态 0正常 1迟到 2早退 3缺卡 4未打卡(还没到打卡时间)")
@Schema(description = "打卡状态 0正常 1迟到 2早退 3缺卡 4未打卡(还没到打卡时间) 5补卡 6请假")
private Integer punchStatus;
@Schema(description = "是否外勤 0否 1是", example = "1")
@ -75,4 +77,4 @@ public class AttendanceOnTheDayDTO {
@Schema(description = "早退时长时间戳")
private Long leaveEarlyTime;
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.service.attendance.punchrecord;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.api.attendance.dto.AttendancePunchRecordDTO;
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;
@ -112,4 +113,11 @@ public interface AttendancePunchRecordService {
* @param editList
*/
void batchUpdate(List<AttendancePunchRecordDO> editList);
}
/**
* 请假修改状态
*
* @param dto
*/
void askingForLeaveAfterwardsToModifyAttendance(AttendancePunchRecordDTO dto);
}

View File

@ -6,9 +6,11 @@ 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.BpmOALeaveDTO;
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.api.attendance.dto.AttendancePunchRecordDTO;
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;
@ -26,7 +28,9 @@ import cn.iocoder.yudao.module.system.service.attendance.punch.dto.AttendanceOnT
import cn.iocoder.yudao.module.system.service.attendance.scheduling.AttendanceSchedulingService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -206,7 +210,7 @@ public class AttendancePunchRecordServiceImpl implements AttendancePunchRecordSe
//
// }
// }
Map<String, String[]> delLeaveMap = new HashMap<>();
for (Map.Entry<Long, Long> entry : map.entrySet()) {
String key = Constants.ATTENDANCE + Constants.UNDERLINE + entry.getKey() + Constants.UNDERLINE; // + 时间
AttendanceGroupDO attendanceGroupDO = groupMap.get(entry.getKey());
@ -221,7 +225,9 @@ public class AttendancePunchRecordServiceImpl implements AttendancePunchRecordSe
List<AttendanceOnTheDayDTO> attendanceOnTheDayDTOS = attendanceService.buildAttendanceOnTheDay(attendanceGroupShiftItemDOS);
for (Long userId : userIds) {
AdminUserDO adminUserDO = userMap.get(userId);
Map<Object, Object> leaveRedisMap = this.getAttendanceLeaveRedisMap(userId);
Long deptId = adminUserDO == null ? null : adminUserDO.getDeptId();
List<String> leaveIds = new ArrayList<>();
for (AttendanceOnTheDayDTO attendanceOnTheDayDTO : attendanceOnTheDayDTOS) {
AttendancePunchRecordDO attendancePunchRecordDO = new AttendancePunchRecordDO();
attendancePunchRecordDO.setUserId(userId);
@ -239,21 +245,67 @@ public class AttendancePunchRecordServiceImpl implements AttendancePunchRecordSe
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());
// -- 请假插入预设
for (Map.Entry<Object, Object> leaveEntry : leaveRedisMap.entrySet()) {
BpmOALeaveDTO dto = JSONUtil.toBean(leaveEntry.getValue().toString(), BpmOALeaveDTO.class);
// - 如果在这个区间之内 - 那么就是需要设为请假的
if ((dto.getStartTime().isBefore(shouldPunchTime) || dto.getStartTime().equals(shouldPunchTime)) &&
(dto.getEndTime().isAfter(shouldPunchTime) || dto.getEndTime().equals(shouldPunchTime))) {
attendancePunchRecordDO.setStatus(AttendanceOnTheDayDTO.ASK_FOR_LEAVE);
attendancePunchRecordDO.setLeaveId(dto.getId());
} else if (dto.getEndTime().isBefore(shouldPunchTime)) {
leaveIds.add(dto.getId().toString());
}
}
String actualDayTime = shouldPunchTime.format(Constants.REPO_DATE_FORMAT);
attendancePunchRecordDO.setActualDayTime(actualDayTime);
attendancePunchRecordDO.setShouldPunchTime(shouldPunchTime);
attendancePunchRecordDO.setLatestPunchTime(shouldPunchTime.plusMinutes(attendanceOnTheDayDTO.getAfterPunchTime()));
attendancePunchRecordDOList.add(attendancePunchRecordDO);
}
stringRedisTemplate.opsForHash().put(key + time, userId.toString(), JSONUtil.toJsonStr(attendanceOnTheDayDTOS));
if (!leaveIds.isEmpty()) {
delLeaveMap.put("leave" + "_" + userId, leaveIds.toArray(new String[0]));
}
}
//设置缓存 2天
stringRedisTemplate.expire(key + time, 2, TimeUnit.DAYS);
}
// -- 删除redis中的请假数据
this.delLeave(delLeaveMap);
// -- 批量
this.saveBatch(attendancePunchRecordDOList);
}
/**
* 删除redis中的请假数据
*
* @param delLeaveMap
*/
private void delLeave(Map<String, String[]> delLeaveMap) {
if (MapUtil.isNotEmpty(delLeaveMap)) {
for (Map.Entry<String, String[]> entry : delLeaveMap.entrySet()) {
stringRedisTemplate.opsForHash().delete(entry.getKey(), entry.getValue());
}
}
}
/**
* 获取用户请假redisMap
*
* @param userId
* @return
*/
private Map<Object, Object> getAttendanceLeaveRedisMap(Long userId) {
HashOperations<String, Object, Object> hashOps = stringRedisTemplate.opsForHash();
String key = "leave" + "_" + userId.toString();
return hashOps.entries(key);
}
@Override
public List<AttendancePunchRecordDO> getNotReminded(LocalDateTime localDateTime) {
String targetDayStr = localDateTime.format(Constants.REPO_DATE_FORMAT);
@ -268,6 +320,17 @@ public class AttendancePunchRecordServiceImpl implements AttendancePunchRecordSe
punchRecordMapper.updateBatch(editList);
}
@Override
public void askingForLeaveAfterwardsToModifyAttendance(AttendancePunchRecordDTO dto) {
punchRecordMapper.update(new AttendancePunchRecordDO()
.setStatus(AttendanceOnTheDayDTO.ASK_FOR_LEAVE)
.setLeaveId(dto.getLeaveId()),
new LambdaUpdateWrapper<AttendancePunchRecordDO>().eq(AttendancePunchRecordDO::getUserId, dto.getUserId())
.ge(AttendancePunchRecordDO::getShouldPunchTime, dto.getStartTime())
.le(AttendancePunchRecordDO::getShouldPunchTime, dto.getEndTime()));
}
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());
@ -287,4 +350,4 @@ public class AttendancePunchRecordServiceImpl implements AttendancePunchRecordSe
return map;
}
}
}

View File

@ -32,4 +32,4 @@
</if>
</where>
</select>
</mapper>
</mapper>