考勤报表导出

This commit is contained in:
aikai 2024-07-03 14:56:53 +08:00
parent b8beed61da
commit 2619fa3015
10 changed files with 493 additions and 85 deletions

View File

@ -49,4 +49,8 @@ public class Constants {
* yyyy-MM-dd格式
*/
public static final DateTimeFormatter REPO_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
/**
* yyyy-MM-dd HH:mm:ss格式
*/
public static final DateTimeFormatter FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
}

View File

@ -10,6 +10,7 @@ import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;

View File

@ -17,7 +17,7 @@ import static cn.iocoder.yudao.framework.common.util.date.DateUtils.TIME_ZONE_DE
@Accessors(chain = true)
public class ExportAttendanceExcelDTO {
@Schema(description = "报表类型 1月度统计 2每日统计 3打卡记录")
@Schema(description = "报表类型 1月度统计 2每日统计")
private Integer type;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)

View File

@ -7,6 +7,8 @@ import lombok.Data;
public class CalculateNum {
@Schema(description = "总考勤时间")
long totalWorkingHours = 0L;
@Schema(description = "总考勤时间中文")
String totalWorkingHoursStr;
@Schema(description = "总出勤天数")
int totalAttendanceDays = 0;
@Schema(description = "总休息天数")
@ -25,6 +27,10 @@ public class CalculateNum {
String totalEarlyDeparturesTimeStr;
@Schema(description = "缺卡总次数")
int totalMissingCardsNumber = 0;
@Schema(description = "上班缺卡总次数")
int totalUpMissingCardsNumber = 0;
@Schema(description = "下班缺卡总次数")
int totalDownMissingCardsNumber = 0;
@Schema(description = "矿工总天数")
int totalMinerDays = 0;
@Schema(description = "外勤次数")

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
@ -138,4 +139,7 @@ public class AttendancePunchRecordDO extends BaseDO {
* 是否已提醒 0否 1是
*/
private Integer remindFlag;
@TableField(exist = false)
private String deptName;
}

View File

@ -5,8 +5,12 @@ 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.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;
import java.util.List;
/**
* 用户打卡记录 Mapper
@ -35,4 +39,12 @@ public interface AttendancePunchRecordMapper extends BaseMapperX<AttendancePunch
.orderByDesc(AttendancePunchRecordDO::getId));
}
/**
* 统计
*
* @param userList
* @param dateList
* @return
*/
List<AttendancePunchRecordDO> statistics(@Param("userList") List<Long> userList, @Param("dateList") List<String> dateList);
}

View File

@ -21,9 +21,11 @@ import cn.iocoder.yudao.module.system.dal.dataobject.attendance.group.Attendance
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.dept.PostDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.group.AttendanceGroupMapper;
import cn.iocoder.yudao.module.system.dal.mysql.attendance.punchrecord.AttendancePunchRecordMapper;
import cn.iocoder.yudao.module.system.dal.mysql.dept.PostMapper;
import cn.iocoder.yudao.module.system.handler.PunchHandler;
import cn.iocoder.yudao.module.system.service.attendance.group.AttendanceGroupService;
import cn.iocoder.yudao.module.system.service.attendance.groupshift.AttendanceGroupShiftService;
@ -34,6 +36,7 @@ import cn.iocoder.yudao.module.system.service.attendance.punch.dto.AttendanceOnT
import cn.iocoder.yudao.module.system.service.attendance.punchrecord.AttendancePunchRecordService;
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import lombok.extern.slf4j.Slf4j;
@ -44,6 +47,9 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
@ -85,6 +91,8 @@ public class AttendanceServiceImpl implements AttendanceService {
private AttendanceGroupMapper attendanceGroupMapper;
@Resource
private AttendanceGroupUserService attendanceGroupUserService;
@Resource
private PostMapper postMapper;
// 定义一些常量以提高代码的可读性和可维护性
@ -608,7 +616,6 @@ public class AttendanceServiceImpl implements AttendanceService {
@Override
public TeamAttendanceStatisticsByCycleVO tesmStatisticsByCycle(TeamAttendanceStatisticsByCycleDTO dto) {
TeamAttendanceStatisticsByCycleVO vo = new TeamAttendanceStatisticsByCycleVO();
TeamAttendanceStatisticsByCycleVO.TeamAttendanceStatisticsNumVO teamAttendanceStatisticsNumVO = new TeamAttendanceStatisticsByCycleVO.TeamAttendanceStatisticsNumVO();
//查询考勤组
AttendanceGroupDO attendanceGroupDO = attendanceGroupService.getGroup(dto.getGroupId());
// - 判断当前用户是否有权限查看
@ -792,11 +799,189 @@ public class AttendanceServiceImpl implements AttendanceService {
} else {
userList = adminUserService.getAllList(CommonStatusEnum.ENABLE.getStatus(), null, dto.getTargetIds());
}
// -- 统计
List<String> dateList = DateUtils.betweenDayList(dto.getStartTime(), dto.getEndTime());
if (dto.getType() == 1) {
this.monthlyStatistics(response, userList, dateList);
String hh_mm = LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm"));
String first = CollectionUtil.getFirst(dateList);
String last = CollectionUtil.getLast(dateList);
//查询出数据 -- 时间周期 - 用户列表
List<AttendancePunchRecordDO> list = attendancePunchRecordMapper.statistics(userList.stream().map(AdminUserDO::getId).collect(Collectors.toList()), dateList);
//获取用户列表
Map<Long, AdminUserDO> userMap = new HashMap<>();
if (CollectionUtil.isNotEmpty(userList)) {
userMap = userList.stream().collect(Collectors.toMap(AdminUserDO::getId, Function.identity()));
}
List<PostDO> userPostList = postMapper.selectList();
// 根据id分组
Map<Long, PostDO> postMap = userPostList.stream().collect(Collectors.toMap(PostDO::getId, Function.identity()));
if (dto.getType() == 1) {
String headTitle = String.format("月度汇总 统计日期:%s 至 %s", first, last);
String detailedHead = String.format("月度汇总 统计日期:%s 至 %s %s", first, last, hh_mm);
this.monthlyStatistics(response, userList, dateList, headTitle, detailedHead, list);
} else {
String headTitle = String.format("每日统计 统计日期:%s 至 %s", first, last);
String detailedHead = String.format("报表生成时间:%s %s", last, hh_mm);
this.dayStatistics(response, userMap, postMap, dateList, headTitle, detailedHead, list);
}
}
/**
* 按日导出
*
* @param response
* @param userMap
* @param postMap
* @param dateList
* @param headTitle
* @param detailedHead
* @param list
*/
private void dayStatistics(HttpServletResponse response, Map<Long, AdminUserDO> userMap, Map<Long, PostDO> postMap, List<String> dateList, String headTitle, String detailedHead, List<AttendancePunchRecordDO> list) {
List<List<String>> data = new ArrayList<>();
// -- 根据部门分组 - 根据考勤组分组
Map<Long, List<AttendancePunchRecordDO>> userPunchMap = list.stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getUserId));
// -- 先计算下要循环几次 - 一个用户一个部门一个考勤组一个班次一个日期 最大的打卡次数 -
Map<String, List<AttendancePunchRecordDO>> map = list.stream().collect(Collectors.groupingBy(
a -> a.getUserId() + "_"
+ a.getDeptId() + "_"
+ a.getAttendanceGroupId() + "_"
+ a.getAttendanceGroupShiftId() + "_"
+ a.getDayTime()));
int maxSize = map.values().stream().mapToInt(List::size).max().orElse(0);
// -- 如果膜2大于0
maxSize = (maxSize % 2) > 0 ? maxSize / 2 + 1 : maxSize / 2;
//先根据人员分组 - 再根据部门分组 - 再根据考勤组分组 - 再根据日期分组
long time = System.currentTimeMillis();
for (Map.Entry<Long, List<AttendancePunchRecordDO>> entry : userPunchMap.entrySet()) {
AdminUserDO adminUserDO = userMap.get(entry.getKey());
List<String> postNames = this.getPostNames(adminUserDO, postMap);
Map<Long, List<AttendancePunchRecordDO>> deptMap = entry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getDeptId));
for (Map.Entry<Long, List<AttendancePunchRecordDO>> deptEntry : deptMap.entrySet()) {
Map<Long, List<AttendancePunchRecordDO>> groupMap = deptEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getAttendanceGroupId));
for (Map.Entry<Long, List<AttendancePunchRecordDO>> groupEntry : groupMap.entrySet()) {
//按日期分组
Map<String, List<AttendancePunchRecordDO>> dayMap = groupEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getDayTime, TreeMap::new, Collectors.toList()));
for (String dateStr : dateList) {
List<AttendancePunchRecordDO> items = dayMap.get(dateStr);
Map<Long, List<AttendancePunchRecordDO>> groupShiftMap = new HashMap<>();
if (CollectionUtil.isNotEmpty(items)) {
groupShiftMap = items.stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getAttendanceGroupShiftId));
} else {
groupShiftMap.put(-1L, Collections.emptyList());
}
for (Map.Entry<Long, List<AttendancePunchRecordDO>> groupShiftEntry : groupShiftMap.entrySet()) {
List<String> row = new ArrayList<>();
row.add(adminUserDO.getNickname());
row.add(groupEntry.getValue().get(0).getAttendanceGroupName());
row.add(groupEntry.getValue().get(0).getDeptName());
row.add(String.join("/", postNames));
row.add(dateStr);
row.add(CollectionUtil.isEmpty(groupShiftEntry.getValue()) ? "休息" : groupShiftEntry.getValue().get(0).getAttendanceGroupShiftName());
Map<Long, List<AttendancePunchRecordDO>> workMap = groupShiftEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getAttendanceGroupShiftItemId, TreeMap::new, Collectors.toList()));
// TODO: 2024/7/2 这里可能会有排序问题 具体看数据在调试
for (Map.Entry<Long, List<AttendancePunchRecordDO>> groupShiftItemEntry : workMap.entrySet()) {
for (AttendancePunchRecordDO attendancePunchRecordDO : groupShiftItemEntry.getValue()) {
row.add(attendancePunchRecordDO.getPunchTime() == null ? "/" : attendancePunchRecordDO.getPunchTime().format(Constants.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND));
row.add(this.statusToStr(attendancePunchRecordDO.getStatus()));
}
}
if (maxSize > workMap.entrySet().size()) {
for (int i = 0; i < maxSize - workMap.entrySet().size(); i++) {
row.add("/");
row.add("/");
row.add("/");
row.add("/");
}
}
//工时
String workHour = this.calculateAverageWorkingHour(workMap);
row.add(workHour);
//迟到时长
String beLate = this.calculateBeLate(workMap);
row.add(beLate);
//早退时长
String leaveEarly = this.calculateEarlyDepartures(workMap);
row.add(leaveEarly);
Map<Integer, Integer> missingCardsMap = this.calculateCommuteMissingCardsList(workMap);
row.add(missingCardsMap.get(Constants.ZERO).toString());
row.add(missingCardsMap.get(Constants.ONE).toString());
data.add(row);
}
}
}
}
}
log.info("考勤按日统计耗时:{}", (System.currentTimeMillis() - time));
// 这里 需要指定写用哪个class去写然后写到第一个sheet名字为模板 然后文件流会自动关闭
try {
EasyExcel.write(response.getOutputStream())
.head(generateDailyHead(headTitle, detailedHead, maxSize))
.autoCloseStream(false)
.excelType(ExcelTypeEnum.XLS)
.registerWriteHandler(new CustomCellStyleHandler())
.sheet("考勤统计按日导出")
.doWrite(data);
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("考勤统计", StandardCharsets.UTF_8.name()));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 获取岗位名称
*
* @param adminUserDO
* @param postMap
* @return
*/
private List<String> getPostNames(AdminUserDO adminUserDO, Map<Long, PostDO> postMap) {
if (adminUserDO.getPostIds() != null) {
return adminUserDO.getPostIds().stream()
.map(postMap::get)
.filter(Objects::nonNull)
.map(PostDO::getName)
.collect(Collectors.toList());
}
return Collections.emptyList();
}
/**
* 状态转换
*
* @param status
* @return
*/
private String statusToStr(int status) {
if (status == 0) {
return "正常";
} else if (status == 1) {
return "迟到";
} else if (status == 2) {
return "早退";
} else if (status == 3) {
return "缺卡";
} else if (status == 4) {
return "未打卡";
} else if (status == 5) {
return "补卡";
}
return "";
}
/**
@ -805,37 +990,104 @@ public class AttendanceServiceImpl implements AttendanceService {
* @param response
* @param userList
* @param dateList
* @param headTitle
* @param detailedHead
* @param list
*/
private void monthlyStatistics(HttpServletResponse response, List<AdminUserDO> userList, List<String> dateList) {
//查询出数据 -- 时间周期 - 用户列表
List<AttendancePunchRecordDO> list = attendancePunchRecordMapper.selectList(new LambdaQueryWrapper<AttendancePunchRecordDO>()
.in(AttendancePunchRecordDO::getUserId, userList)
.in(AttendancePunchRecordDO::getDayTime, dateList));
private void monthlyStatistics(HttpServletResponse response, List<AdminUserDO> userList, List<String> dateList, String headTitle, String detailedHead, List<AttendancePunchRecordDO> list) {
List<List<String>> data = new ArrayList<>();
// -- 根据部门分组 - 根据考勤组分组
Map<Long, List<AttendancePunchRecordDO>> userPunchMap = list.stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getUserId));
//获取用户列表
Map<Long, AdminUserDO> userMap = new HashMap<>();
if (CollectionUtil.isNotEmpty(userList)) {
userMap = userList.stream().collect(Collectors.toMap(AdminUserDO::getId, Function.identity()));
}
List<PostDO> userPostList = postMapper.selectList();
// 根据id分组
Map<Long, PostDO> postMap = userPostList.stream().collect(Collectors.toMap(PostDO::getId, Function.identity()));
//先根据人员分组 - 再根据部门分组 - 再根据考勤组分组 - 再根据日期分组
for (Map.Entry<Long, List<AttendancePunchRecordDO>> entry : userPunchMap.entrySet()) {
AdminUserDO adminUserDO = userMap.get(entry.getKey());
List<String> postNames = this.getPostNames(adminUserDO, postMap);
Map<Long, List<AttendancePunchRecordDO>> deptMap = entry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getDeptId));
for (Map.Entry<Long, List<AttendancePunchRecordDO>> deptEntry : deptMap.entrySet()) {
Map<Long, List<AttendancePunchRecordDO>> groupMap = deptEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getAttendanceGroupId));
for (Map.Entry<Long, List<AttendancePunchRecordDO>> groupEntry : groupMap.entrySet()) {
CalculateNum calculateNum = new CalculateNum();
//按日期分组
Map<String, List<AttendancePunchRecordDO>> dayMap = groupEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getDayTime));
for (Map.Entry<String, List<AttendancePunchRecordDO>> dayEntry : dayMap.entrySet()) {
// -- 按照班次子表分组
Map<Long, List<AttendancePunchRecordDO>> workMap = dayEntry.getValue().stream().collect(Collectors.groupingBy(AttendancePunchRecordDO::getAttendanceGroupShiftItemId));
this.calculateAverageWorkingHour(workMap, calculateNum);
// -- 出考勤天数
this.calculateAttendanceDays(dayEntry.getValue(), calculateNum);
// -- 迟到
this.calculateBeLate(workMap, calculateNum);
// -- 早退
this.calculateEarlyDepartures(workMap, calculateNum);
// -- 缺卡
this.calculateCommuteMissingCardsList(workMap, calculateNum);
// -- 旷工
this.calculateMiner(dayEntry.getValue(), calculateNum);
}
//休息天数
this.calculateRestDay(dateList, dayMap.keySet().stream().distinct().collect(Collectors.toList()), calculateNum);
//考勤总时间中文
calculateNum.setTotalWorkingHoursStr(DateUtil.formatBetween(calculateNum.getTotalWorkingHours(), BetweenFormatter.Level.MINUTE));
//迟到总时间中文
calculateNum.setTotalLateArrivalsTimeStr(DateUtil.formatBetween(calculateNum.getTotalLateArrivalsTime(), BetweenFormatter.Level.MINUTE));
//早退总时间中文
calculateNum.setTotalEarlyDeparturesTimeStr(DateUtil.formatBetween(calculateNum.getTotalEarlyDeparturesTime(), BetweenFormatter.Level.MINUTE));
List<String> row = new ArrayList<>();
row.add(adminUserDO.getNickname());
row.add(groupEntry.getValue().get(0).getAttendanceGroupName());
row.add(groupEntry.getValue().get(0).getDeptName());
row.add(String.join("/", postNames));
row.add(String.valueOf(calculateNum.getTotalAttendanceDays()));
row.add(String.valueOf(calculateNum.getTotalRestDays()));
row.add(String.valueOf(calculateNum.getTotalWorkingHoursStr()));
row.add(String.valueOf(calculateNum.getTotalLateArrivalsNumber()));
row.add(String.valueOf(calculateNum.getTotalLateArrivalsTimeStr()));
row.add(String.valueOf(calculateNum.getTotalEarlyDeparturesNumber()));
row.add(String.valueOf(calculateNum.getTotalEarlyDeparturesTimeStr()));
row.add(String.valueOf(calculateNum.getTotalUpMissingCardsNumber()));
row.add(String.valueOf(calculateNum.getTotalDownMissingCardsNumber()));
row.add(String.valueOf(calculateNum.getTotalMinerDays()));
data.add(row);
}
}
}
try {
EasyExcel.write(response.getOutputStream())
.head(generateHead(headTitle, detailedHead))
.autoCloseStream(false)
.excelType(ExcelTypeEnum.XLS)
.registerWriteHandler(new CustomCellStyleHandler())
.sheet("考勤统计按月导出")
.doWrite(data);
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("考勤统计", StandardCharsets.UTF_8.name()));
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
String fileName = "/Users/aikai/Downloads/" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写然后写到第一个sheet名字为模板 然后文件流会自动关闭
EasyExcel.write(fileName)
.head(generateHead())
.registerWriteHandler(new CustomCellStyleHandler())
// .registerWriteHandler(new LoopMergeStrategy())
// .registerWriteHandler(loopMergeStrategy)
.sheet("模板")
.doWrite(generateData());
}
public static List<List<String>> generateHead() {
public static List<List<String>> generateHead(String headTitle, String detailedHead) {
List<List<String>> head = new ArrayList<>();
String headTitle = "月度汇总 统计日期2024-06-01 至 2024-06-13";
String detailedHead = "月度汇总 统计日期2024-06-01 至 2024-06-13 10:48";
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, "休息天数", "休息天数"));
@ -853,70 +1105,48 @@ 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, "考勤结果", "1"));
head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "2"));
head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "3"));
head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "4"));
head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "5"));
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, "考勤结果", "1"));
// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "2"));
// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "3"));
// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "4"));
// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", "5"));
// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", ""));
// head.add(Arrays.asList(headTitle, detailedHead, "考勤结果", ""));
// 添加更多表头
return head;
}
public static List<List<Object>> generateData() {
List<List<Object>> data = new ArrayList<>();
List<Object> row1 = new ArrayList<>();
row1.add("艾楷");
row1.add("测试考勤");
row1.add("");
row1.add("");
row1.add("");
row1.add("22026829221062585");
row1.add(1);
row1.add(2);
row1.add(3);
row1.add(4);
row1.add(5);
row1.add(6);
row1.add(7);
row1.add(8);
row1.add(9);
row1.add(10);
row1.add(10);
row1.add(12);
// 添加更多数据
List<Object> row2 = new ArrayList<>();
row2.add("谢鸿飞");
row2.add("测试考勤");
row2.add("");
row2.add("");
row2.add("");
row2.add("066557433135769889");
row2.add(1);
row2.add(2);
row2.add(3);
row2.add(4);
row2.add(4);
row2.add(4);
row2.add(4);
row2.add(4);
row2.add(4);
row2.add(4);
row2.add(4);
row2.add(4);
// 添加更多数据
data.add(row1);
data.add(row2);
return data;
public static List<List<String>> generateDailyHead(String headTitle, String detailedHead, int size) {
List<List<String>> head = new ArrayList<>();
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, "班次", "班次"));
for (int i = 1; i <= size; i++) {
head.add(Arrays.asList(headTitle, detailedHead, "上班" + i + "打卡时间", "上班" + i + "打卡时间"));
head.add(Arrays.asList(headTitle, detailedHead, "上班" + i + "打卡结果", "上班" + i + "打卡结果"));
head.add(Arrays.asList(headTitle, detailedHead, "下班" + i + "打卡时间", "下班" + i + "打卡时间"));
head.add(Arrays.asList(headTitle, detailedHead, "下班" + i + "打卡结果", "下班" + i + "打卡结果"));
}
// 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, "早退次数", "早退次数"));
head.add(Arrays.asList(headTitle, detailedHead, "早退时长", "早退时长"));
head.add(Arrays.asList(headTitle, detailedHead, "上班缺卡次数", "上班缺卡次数"));
head.add(Arrays.asList(headTitle, detailedHead, "下班缺卡次数", "下班缺卡次数"));
// head.add(Arrays.asList(headTitle, detailedHead, "旷工天数", "旷工天数"));
return head;
}
/**
* 外勤计算
*
@ -1012,6 +1242,18 @@ public class AttendanceServiceImpl implements AttendanceService {
return list;
}
/**
* 休息时间计算
*
* @param dateList
* @param attendanceTime
* @param calculateNum
* @return
*/
private void calculateRestDay(List<String> dateList, List<String> attendanceTime, CalculateNum calculateNum) {
List<String> subtract = new ArrayList<>(CollectionUtil.subtract(dateList, attendanceTime));
calculateNum.setTotalRestDays(subtract.size());
}
/**
* @param day
@ -1057,6 +1299,51 @@ public class AttendanceServiceImpl implements AttendanceService {
calculateNum.setTotalMissingCardsNumber(calculateNum.getTotalMissingCardsNumber() + todayMissingCardsNumber);
}
private void calculateCommuteMissingCardsList(Map<Long, List<AttendancePunchRecordDO>> workMap, CalculateNum calculateNum) {
int todayUpMissingCardsNumber = 0;
int todayDownMissingCardsNumber = 0;
for (Map.Entry<Long, List<AttendancePunchRecordDO>> workEntry : workMap.entrySet()) {
if (CollectionUtil.isNotEmpty(workEntry.getValue())) {
// -- 缺卡计算
List<AttendancePunchRecordDO> missingCardsList = workEntry.getValue().stream().filter(a -> AttendanceOnTheDayDTO.PUNCH_STATUS_MISS.equals(a.getStatus())).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(missingCardsList)) {
long upNum = missingCardsList.stream().filter(a -> Constants.ZERO.equals(a.getWorkType())).count();
long downNum = missingCardsList.stream().filter(a -> Constants.ONE.equals(a.getWorkType())).count();
todayUpMissingCardsNumber += upNum;
todayDownMissingCardsNumber += downNum;
}
}
}
calculateNum.setTotalUpMissingCardsNumber(calculateNum.getTotalUpMissingCardsNumber() + todayUpMissingCardsNumber);
calculateNum.setTotalDownMissingCardsNumber(calculateNum.getTotalDownMissingCardsNumber() + todayDownMissingCardsNumber);
}
/**
* 迟到次数
*
* @param workMap
*/
private Map<Integer, Integer> calculateCommuteMissingCardsList(Map<Long, List<AttendancePunchRecordDO>> workMap) {
int todayUpMissingCardsNumber = 0;
int todayDownMissingCardsNumber = 0;
for (Map.Entry<Long, List<AttendancePunchRecordDO>> workEntry : workMap.entrySet()) {
if (CollectionUtil.isNotEmpty(workEntry.getValue())) {
// -- 缺卡计算
List<AttendancePunchRecordDO> missingCardsList = workEntry.getValue().stream().filter(a -> AttendanceOnTheDayDTO.PUNCH_STATUS_MISS.equals(a.getStatus())).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(missingCardsList)) {
long upNum = missingCardsList.stream().filter(a -> Constants.ZERO.equals(a.getWorkType())).count();
long downNum = missingCardsList.stream().filter(a -> Constants.ONE.equals(a.getWorkType())).count();
todayUpMissingCardsNumber += upNum;
todayDownMissingCardsNumber += downNum;
}
}
}
Map<Integer, Integer> map = new HashMap<>();
map.put(Constants.ZERO, todayUpMissingCardsNumber);
map.put(Constants.ONE, todayDownMissingCardsNumber);
return map;
}
/**
* 早退计算
*
@ -1111,6 +1398,20 @@ public class AttendanceServiceImpl implements AttendanceService {
calculateNum.setTotalEarlyDeparturesTime(calculateNum.getTotalEarlyDeparturesTime() + todayEarlyDeparturesTime);
}
private String calculateEarlyDepartures(Map<Long, List<AttendancePunchRecordDO>> workMap) {
long todayEarlyDeparturesTime = 0L;
for (Map.Entry<Long, List<AttendancePunchRecordDO>> workEntry : workMap.entrySet()) {
if (CollectionUtil.isNotEmpty(workEntry.getValue())) {
// -- 早退计算
List<AttendancePunchRecordDO> earlyDeparturesList = workEntry.getValue().stream().filter(a -> AttendanceOnTheDayDTO.PUNCH_STATUS_LEAVE_EARLY.equals(a.getStatus())).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(earlyDeparturesList)) {
todayEarlyDeparturesTime += earlyDeparturesList.stream().mapToLong(AttendancePunchRecordDO::getLeaveEarlyTime).sum();
}
}
}
return DateUtil.formatBetween(todayEarlyDeparturesTime, BetweenFormatter.Level.MINUTE);
}
/**
* 计算迟到
*
@ -1166,6 +1467,26 @@ public class AttendanceServiceImpl implements AttendanceService {
calculateNum.setTotalLateArrivalsTime(calculateNum.getTotalLateArrivalsTime() + todayLateArrivalsTime);
}
/**
* 迟到时长
*
* @param workMap
* @return
*/
private String calculateBeLate(Map<Long, List<AttendancePunchRecordDO>> workMap) {
long todayLateArrivalsTime = 0L;
for (Map.Entry<Long, List<AttendancePunchRecordDO>> workEntry : workMap.entrySet()) {
if (CollectionUtil.isNotEmpty(workEntry.getValue())) {
// -- 迟到计算
List<AttendancePunchRecordDO> beLateList = workEntry.getValue().stream().filter(a -> AttendanceOnTheDayDTO.PUNCH_STATUS_LATE.equals(a.getStatus())).collect(Collectors.toList());
if (CollectionUtil.isNotEmpty(beLateList)) {
todayLateArrivalsTime += beLateList.stream().mapToLong(AttendancePunchRecordDO::getLateTime).sum();
}
}
}
return DateUtil.formatBetween(todayLateArrivalsTime, BetweenFormatter.Level.MINUTE);
}
/**
* 计算考勤工时
*
@ -1184,6 +1505,17 @@ public class AttendanceServiceImpl implements AttendanceService {
return vo;
}
/**
* 计算工时
*
* @param workMap
* @return
*/
private String calculateAverageWorkingHour(Map<Long, List<AttendancePunchRecordDO>> workMap) {
long todayWorkingHours = this.calculateAverageWorkingHourDay(workMap);
return DateUtil.formatBetween(todayWorkingHours, BetweenFormatter.Level.MINUTE);
}
/**
* 计算考勤工时
*
@ -1209,6 +1541,30 @@ public class AttendanceServiceImpl implements AttendanceService {
return todayWorkingHours;
}
/**
* 计算工时 - 当天
*
* @param workMap
* @return
*/
private long calculateAverageWorkingHourDay(Map<Long, List<AttendancePunchRecordDO>> workMap) {
long todayWorkingHours = 0L;
for (Map.Entry<Long, List<AttendancePunchRecordDO>> workEntry : workMap.entrySet()) {
if (CollectionUtil.isNotEmpty(workEntry.getValue())) {
// -- 工时计算 - 有上班和下班两个卡 才计算到工时里面
if (workEntry.getValue().size() >= 2) {
AttendancePunchRecordDO first = CollectionUtil.getFirst(workEntry.getValue());
AttendancePunchRecordDO last = CollectionUtil.getLast(workEntry.getValue());
if (ObjectUtil.isNotEmpty(first.getPunchTime()) && ObjectUtil.isNotEmpty(last.getPunchTime())) {
long time = Math.abs(LocalDateTimeUtil.between(first.getPunchTime(), last.getPunchTime(), ChronoUnit.MILLIS));
todayWorkingHours += time;
}
}
}
}
return todayWorkingHours;
}
/**
* 计算出勤天数

View File

@ -25,7 +25,7 @@ public class CustomCellStyleHandler implements CellWriteHandler {
Workbook workbook = writeSheetHolder.getSheet().getWorkbook();
CellStyle cellStyle = workbook.createCellStyle();
writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), 5120);
writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), 7120);
// 设置水平和垂直居中对齐
cellStyle.setAlignment(HorizontalAlignment.CENTER);
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
@ -44,12 +44,14 @@ public class CustomCellStyleHandler implements CellWriteHandler {
// 头的策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 背景设置为红色
headWriteCellStyle.setFillForegroundColor(IndexedColors.YELLOW.getIndex());
headWriteCellStyle.setFillForegroundColor(IndexedColors.LIGHT_YELLOW.getIndex());
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short) 20);
headWriteCellStyle.setWriteFont(headWriteFont);
WriteCellStyle.merge(headWriteCellStyle, cellDataList.get(0).getOrCreateStyle());
}
} else {
}
cell.setCellStyle(cellStyle);
}

View File

@ -9,4 +9,27 @@
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
<select id="statistics"
resultType="cn.iocoder.yudao.module.system.dal.dataobject.attendance.punchrecord.AttendancePunchRecordDO">
select
a.*,
b.name as deptName
from kq_attendance_punch_record a
left join system_dept as b on a.dept_id = b.id
<where>
a.deleted = 0
<if test="userList != null and userList.size() > 0">
and a.user_id in
<foreach collection="userList" item="userId" separator="," open="(" close=")">
#{userId}
</foreach>
</if>
<if test="dateList != null and dateList.size() > 0">
and a.day_time in
<foreach collection="dateList" item="dayTime" separator="," open="(" close=")">
#{dayTime}
</foreach>
</if>
</where>
</select>
</mapper>