优化
This commit is contained in:
@ -1,9 +1,13 @@
|
|||||||
package org.dromara.contractor.mapper;
|
package org.dromara.contractor.mapper;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.Insert;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
import org.dromara.contractor.domain.SubUserSalaryDetail;
|
import org.dromara.contractor.domain.SubUserSalaryDetail;
|
||||||
import org.dromara.contractor.domain.vo.usersalarydetail.SubUserSalaryDetailVo;
|
import org.dromara.contractor.domain.vo.usersalarydetail.SubUserSalaryDetailVo;
|
||||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 员工每日工资Mapper接口
|
* 员工每日工资Mapper接口
|
||||||
*
|
*
|
||||||
@ -12,4 +16,15 @@ import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
|||||||
*/
|
*/
|
||||||
public interface SubUserSalaryDetailMapper extends BaseMapperPlus<SubUserSalaryDetail, SubUserSalaryDetailVo> {
|
public interface SubUserSalaryDetailMapper extends BaseMapperPlus<SubUserSalaryDetail, SubUserSalaryDetailVo> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Insert({
|
||||||
|
"<script>",
|
||||||
|
"INSERT INTO sub_user_salary_detail (project_id, team_id, user_id, user_name, report_date, work_hour, daily_salary, total_salary, create_by, update_by, remark) VALUES ",
|
||||||
|
"<foreach collection='list' item='item' separator=','>",
|
||||||
|
"(#{item.projectId}, #{item.teamId}, #{item.userId}, #{item.userName}, #{item.reportDate}, #{item.workHour}, #{item.dailySalary}, #{item.totalSalary}, #{userId}, #{userId}, #{item.remark})",
|
||||||
|
"</foreach>",
|
||||||
|
"</script>"
|
||||||
|
})
|
||||||
|
Boolean insertAll(@Param("list")List<SubUserSalaryDetail> list, @Param("userId") Long userId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import jakarta.annotation.Resource;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.pdfbox.io.IOUtils;
|
import org.apache.pdfbox.io.IOUtils;
|
||||||
import org.apache.poi.ss.usermodel.Sheet;
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
import org.apache.poi.ss.usermodel.Workbook;
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
@ -30,6 +31,7 @@ import org.dromara.common.core.utils.ObjectUtils;
|
|||||||
import org.dromara.common.core.utils.StringUtils;
|
import org.dromara.common.core.utils.StringUtils;
|
||||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
import org.dromara.common.mybatis.core.page.TableDataInfo;
|
||||||
|
import org.dromara.common.satoken.utils.LoginHelper;
|
||||||
import org.dromara.common.utils.IdCardEncryptorUtil;
|
import org.dromara.common.utils.IdCardEncryptorUtil;
|
||||||
import org.dromara.contractor.domain.SubConstructionUser;
|
import org.dromara.contractor.domain.SubConstructionUser;
|
||||||
import org.dromara.contractor.domain.SubUserSalaryDetail;
|
import org.dromara.contractor.domain.SubUserSalaryDetail;
|
||||||
@ -58,6 +60,7 @@ import org.springframework.beans.BeanUtils;
|
|||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import software.amazon.awssdk.utils.CollectionUtils;
|
import software.amazon.awssdk.utils.CollectionUtils;
|
||||||
|
|
||||||
@ -72,6 +75,7 @@ import java.time.LocalDate;
|
|||||||
import java.time.YearMonth;
|
import java.time.YearMonth;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import org.dromara.contractor.excel.SalaryExcelReader;
|
import org.dromara.contractor.excel.SalaryExcelReader;
|
||||||
|
|
||||||
@ -83,6 +87,7 @@ import org.dromara.contractor.excel.SalaryExcelReader;
|
|||||||
*/
|
*/
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Service
|
@Service
|
||||||
|
@Slf4j
|
||||||
public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDetailMapper, SubUserSalaryDetail>
|
public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDetailMapper, SubUserSalaryDetail>
|
||||||
implements ISubUserSalaryDetailService {
|
implements ISubUserSalaryDetailService {
|
||||||
|
|
||||||
@ -525,19 +530,21 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void importData(MultipartFile file, String month) {
|
public void importData(MultipartFile file, String month) {
|
||||||
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
// 1. 校验文件合法性
|
// 1. 校验文件合法性
|
||||||
if (file.isEmpty()) {
|
if (file.isEmpty()) {
|
||||||
throw new IllegalArgumentException("上传的Excel文件不能为空!");
|
throw new IllegalArgumentException("上传的Excel文件不能为空!");
|
||||||
}
|
}
|
||||||
// 校验文件格式(仅允许xlsx/xls)
|
|
||||||
String originalFilename = file.getOriginalFilename();
|
String originalFilename = file.getOriginalFilename();
|
||||||
if (originalFilename == null || !(originalFilename.endsWith(".xlsx"))) {
|
if (originalFilename == null || !(originalFilename.endsWith(".xlsx"))) {
|
||||||
throw new IllegalArgumentException("仅支持上传Excel文件(.xlsx 格式)!");
|
throw new IllegalArgumentException("仅支持上传Excel文件(.xlsx 格式)!");
|
||||||
}
|
}
|
||||||
|
|
||||||
List<DynamicSalaryData> dataList;
|
List<DynamicSalaryData> dataList;
|
||||||
try {
|
try {
|
||||||
// 2. 将 MultipartFile 转为 InputStream,传给工具类解析
|
|
||||||
dataList = SalaryExcelReader.readAllAttendanceByStream(file.getInputStream(), month);
|
dataList = SalaryExcelReader.readAllAttendanceByStream(file.getInputStream(), month);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Excel流解析失败:" + e.getMessage(), e);
|
throw new RuntimeException("Excel流解析失败:" + e.getMessage(), e);
|
||||||
@ -546,42 +553,79 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
|||||||
throw new ServiceException("未读取到数据");
|
throw new ServiceException("未读取到数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Map<String, Double>> dataMap = dataList.stream().collect(Collectors.toMap(vo -> idCardEncryptorUtil.encrypt(vo.getIdCard()), DynamicSalaryData::getDailyAttendance));
|
// 2. 数据预处理 - 使用并行流提高处理速度
|
||||||
Set<String> cards = dataMap.keySet();
|
Map<String, Map<String, Double>> dataMap = dataList.parallelStream()
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
vo -> idCardEncryptorUtil.encrypt(vo.getIdCard()),
|
||||||
|
DynamicSalaryData::getDailyAttendance
|
||||||
|
));
|
||||||
|
|
||||||
|
Set<String> cards = dataMap.keySet();
|
||||||
month = dataList.getFirst().getMonth();
|
month = dataList.getFirst().getMonth();
|
||||||
YearMonth parse = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM"));
|
YearMonth parse = YearMonth.parse(month, DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||||
LocalDate start = parse.atDay(1);
|
LocalDate start = parse.atDay(1);
|
||||||
LocalDate end = parse.atEndOfMonth();
|
LocalDate end = parse.atEndOfMonth();
|
||||||
|
|
||||||
//人员数据
|
// 3. 分批处理人员数据 - 避免一次性加载大量数据
|
||||||
List<SubConstructionUser> list = constructionUserService.list(Wrappers.<SubConstructionUser>lambdaQuery()
|
List<SubConstructionUser> allUsers = new ArrayList<>();
|
||||||
.in(SubConstructionUser::getSfzNumber, cards));
|
int batchSize = 1000; // 批量查询大小
|
||||||
|
|
||||||
List<Long> userIds = list.stream().map(SubConstructionUser::getSysUserId).toList();
|
// 分批查询用户数据
|
||||||
//考勤数据
|
List<List<String>> cardBatches = new ArrayList<>();
|
||||||
List<BusAttendance> attendanceList = attendanceService
|
List<String> cardList = new ArrayList<>(cards);
|
||||||
.list(Wrappers.<BusAttendance>lambdaQuery()
|
for (int i = 0; i < cardList.size(); i += batchSize) {
|
||||||
.in(BusAttendance::getUserId, userIds)
|
int endIndex = Math.min(i + batchSize, cardList.size());
|
||||||
.between(BusAttendance::getClockDate, start, end)
|
cardBatches.add(cardList.subList(i, endIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (List<String> batchCards : cardBatches) {
|
||||||
|
List<SubConstructionUser> batchUsers = constructionUserService.list(
|
||||||
|
Wrappers.<SubConstructionUser>lambdaQuery()
|
||||||
|
.in(SubConstructionUser::getSfzNumber, batchCards)
|
||||||
);
|
);
|
||||||
// 将 attendanceList 转换为 Map<String, BigDecimal> 格式
|
allUsers.addAll(batchUsers);
|
||||||
Map<String, BigDecimal> attendanceSalaryMap = new HashMap<>();
|
}
|
||||||
|
|
||||||
// 按 userId+日期 分组,只保留每组的第一条记录的salary
|
if (CollUtil.isEmpty(allUsers)) {
|
||||||
attendanceList.stream()
|
throw new ServiceException("未找到匹配的人员信息");
|
||||||
.collect(Collectors.groupingBy(
|
}
|
||||||
attendance -> attendance.getUserId() + "_" + attendance.getClockDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
|
|
||||||
Collectors.toList()
|
|
||||||
))
|
|
||||||
.forEach((key, dateList) -> attendanceSalaryMap.put(key, dateList.getFirst().getSalary()));
|
|
||||||
|
|
||||||
|
// 4. 分批处理考勤数据 - 避免内存溢出
|
||||||
|
List<Long> userIds = allUsers.stream()
|
||||||
|
.map(SubConstructionUser::getSysUserId)
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
List<SubUserSalaryDetail> addList = new ArrayList<>();
|
Map<String, BigDecimal> attendanceSalaryMap = new ConcurrentHashMap<>();
|
||||||
for(SubConstructionUser constructionUser : list){
|
|
||||||
Map<String, Double> stringIntegerMap = dataMap.get(constructionUser.getSfzNumber());
|
// 分批查询考勤数据
|
||||||
if(stringIntegerMap != null){
|
for (int i = 0; i < userIds.size(); i += batchSize) {
|
||||||
for(Map.Entry<String, Double> entry : stringIntegerMap.entrySet()){
|
int endIndex = Math.min(i + batchSize, userIds.size());
|
||||||
|
List<Long> batchUserIds = userIds.subList(i, endIndex);
|
||||||
|
|
||||||
|
List<BusAttendance> batchAttendanceList = attendanceService.list(
|
||||||
|
Wrappers.<BusAttendance>lambdaQuery()
|
||||||
|
.in(BusAttendance::getUserId, batchUserIds)
|
||||||
|
.between(BusAttendance::getClockDate, start, end)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 并行处理考勤数据
|
||||||
|
batchAttendanceList.parallelStream()
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
attendance -> attendance.getUserId() + "_" + attendance.getClockDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")),
|
||||||
|
Collectors.toList()
|
||||||
|
))
|
||||||
|
.forEach((key, dateList) -> attendanceSalaryMap.put(key, dateList.getFirst().getSalary()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 批量构建数据 - 使用并行处理
|
||||||
|
List<SubUserSalaryDetail> allAddList = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
|
// 并行处理用户数据构建
|
||||||
|
allUsers.parallelStream().forEach(constructionUser -> {
|
||||||
|
Map<String, Double> userAttendanceMap = dataMap.get(constructionUser.getSfzNumber());
|
||||||
|
if(userAttendanceMap != null){
|
||||||
|
userAttendanceMap.entrySet().parallelStream().forEach(entry -> {
|
||||||
String key = entry.getKey();
|
String key = entry.getKey();
|
||||||
Double value = entry.getValue();
|
Double value = entry.getValue();
|
||||||
|
|
||||||
@ -600,16 +644,35 @@ public class SubUserSalaryDetailServiceImpl extends ServiceImpl<SubUserSalaryDet
|
|||||||
}
|
}
|
||||||
subUserSalaryDetail.setDailySalary(bigDecimal);
|
subUserSalaryDetail.setDailySalary(bigDecimal);
|
||||||
subUserSalaryDetail.setTotalSalary(bigDecimal.multiply(new BigDecimal(value.toString())));
|
subUserSalaryDetail.setTotalSalary(bigDecimal.multiply(new BigDecimal(value.toString())));
|
||||||
addList.add(subUserSalaryDetail);
|
|
||||||
}
|
allAddList.add(subUserSalaryDetail);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. 批量删除和插入 - 分批处理避免数据库压力
|
||||||
|
if (CollUtil.isNotEmpty(allAddList)) {
|
||||||
|
// 批量删除现有数据
|
||||||
|
for (int i = 0; i < userIds.size(); i += batchSize) {
|
||||||
|
int endIndex = Math.min(i + batchSize, userIds.size());
|
||||||
|
List<Long> deleteUserIds = userIds.subList(i, endIndex);
|
||||||
|
|
||||||
|
baseMapper.delete(Wrappers.<SubUserSalaryDetail>lambdaQuery()
|
||||||
|
.in(SubUserSalaryDetail::getUserId, deleteUserIds)
|
||||||
|
.between(SubUserSalaryDetail::getReportDate, start, end)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 批量插入新数据 - 使用自定义批量插入方法
|
||||||
|
for (int i = 0; i < allAddList.size(); i += batchSize) {
|
||||||
|
int endIndex = Math.min(i + batchSize, allAddList.size());
|
||||||
|
List<SubUserSalaryDetail> insertBatch = allAddList.subList(i, endIndex);
|
||||||
|
baseMapper.insertAll(insertBatch,LoginHelper.getUserId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
baseMapper.delete(Wrappers.<SubUserSalaryDetail>lambdaQuery()
|
|
||||||
.in(SubUserSalaryDetail::getUserId, userIds)
|
long endTime = System.currentTimeMillis();
|
||||||
.between(SubUserSalaryDetail::getReportDate, start, end)
|
log.info("工资数据导入完成,耗时: {} ms,处理数据: {} 条", (endTime - startTime), allAddList.size());
|
||||||
);
|
|
||||||
baseMapper.insertBatch(addList);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||||
import org.apache.poi.ss.usermodel.*;
|
import org.apache.poi.ss.usermodel.*;
|
||||||
import org.apache.poi.ss.util.CellRangeAddress;
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
import org.dromara.common.core.constant.DateConstant;
|
import org.dromara.common.core.constant.DateConstant;
|
||||||
import org.dromara.common.core.constant.HttpStatus;
|
import org.dromara.common.core.constant.HttpStatus;
|
||||||
import org.dromara.common.core.exception.ServiceException;
|
import org.dromara.common.core.exception.ServiceException;
|
||||||
@ -1041,7 +1042,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
AttendanceUserDataDetailVo detailVo = new AttendanceUserDataDetailVo();
|
AttendanceUserDataDetailVo detailVo = new AttendanceUserDataDetailVo();
|
||||||
detailVo.setClockDate(key);
|
detailVo.setClockDate(key);
|
||||||
detailVo.setWeek(key.getDayOfWeek().getValue());
|
detailVo.setWeek(key.getDayOfWeek().getValue());
|
||||||
detailVo.setWorkDay(value.size()*0.5);
|
detailVo.setWorkDay(value.size() * 0.5);
|
||||||
workList.add(detailVo);
|
workList.add(detailVo);
|
||||||
}
|
}
|
||||||
vo.setWork(workList);
|
vo.setWork(workList);
|
||||||
@ -1178,164 +1179,170 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void getExportList(AttendanceExportDto dto, HttpServletResponse response) {
|
public void getExportList(AttendanceExportDto dto, HttpServletResponse response) {
|
||||||
|
try (OutputStream outputStream = response.getOutputStream()) {
|
||||||
|
BusProject project = projectService.getById(dto.getProjectId());
|
||||||
|
|
||||||
BusProject project = projectService.getById(dto.getProjectId());
|
if (project == null) {
|
||||||
|
throw new ServiceException("项目不存在");
|
||||||
if (project == null) {
|
|
||||||
throw new ServiceException("项目不存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SubConstructionUser> list = constructionUserService.list(Wrappers.lambdaQuery(SubConstructionUser.class)
|
|
||||||
.eq(SubConstructionUser::getProjectId, dto.getProjectId())
|
|
||||||
.eq(dto.getTeamId() != null, SubConstructionUser::getTeamId, dto.getTeamId())
|
|
||||||
.eq(StrUtil.isNotBlank(dto.getTypeOfWork()), SubConstructionUser::getTypeOfWork, dto.getTypeOfWork())
|
|
||||||
.like(StringUtils.isNotBlank(dto.getUserName()), SubConstructionUser::getUserName, dto.getUserName())
|
|
||||||
);
|
|
||||||
|
|
||||||
if (list.isEmpty()) {
|
|
||||||
throw new ServiceException("未查询到相关人员");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Long> userIds = list.stream().map(SubConstructionUser::getSysUserId).toList();
|
|
||||||
String clockDate = dto.getClockDate();
|
|
||||||
|
|
||||||
YearMonth yearMonth = YearMonth.parse(clockDate, DateTimeFormatter.ofPattern("yyyy-MM"));
|
|
||||||
LocalDate start = yearMonth.atDay(1);
|
|
||||||
LocalDate end = yearMonth.atEndOfMonth();
|
|
||||||
|
|
||||||
int daysInMonth = yearMonth.lengthOfMonth(); // 动态获取天数
|
|
||||||
|
|
||||||
List<BusAttendance> attendanceList = list(Wrappers.lambdaQuery(BusAttendance.class)
|
|
||||||
.in(BusAttendance::getUserId, userIds)
|
|
||||||
.between(BusAttendance::getClockDate, start, end)
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<Long, List<SubConstructionUser>> teamUserMap = list.stream()
|
|
||||||
.collect(Collectors.groupingBy(user ->
|
|
||||||
user.getTeamId() != null ? user.getTeamId() : 0L));
|
|
||||||
Workbook workbook = new HSSFWorkbook();
|
|
||||||
|
|
||||||
for (Map.Entry<Long, List<SubConstructionUser>> entry : teamUserMap.entrySet()) {
|
|
||||||
Long teamId = entry.getKey();
|
|
||||||
System.out.println("teamId:" + teamId);
|
|
||||||
List<SubConstructionUser> users = entry.getValue();
|
|
||||||
SubConstructionUser constructionUser = users.getFirst();
|
|
||||||
String teamName = constructionUser.getTeamName();
|
|
||||||
if (teamId == 0) {
|
|
||||||
teamName = "无班组";
|
|
||||||
}
|
}
|
||||||
if (StringUtils.isBlank(teamName)) {
|
String clockDate = dto.getClockDate();
|
||||||
teamName = teamId.toString();
|
YearMonth yearMonth = YearMonth.parse(clockDate, DateTimeFormatter.ofPattern("yyyy-MM"));
|
||||||
}
|
LocalDate start = yearMonth.atDay(1);
|
||||||
System.out.println("name:" + teamName);
|
LocalDate end = yearMonth.atEndOfMonth();
|
||||||
Sheet sheet = workbook.createSheet(teamName);
|
|
||||||
|
|
||||||
// ==================== 设置列宽 ====================
|
LocalDateTime startTime = LocalDateTime.of(yearMonth.atDay(1), LocalTime.MIN); // 00:00:00
|
||||||
sheet.setColumnWidth(0, 5 * 256); // 序号
|
LocalDateTime endTime = LocalDateTime.of(yearMonth.atEndOfMonth(), LocalTime.MAX);
|
||||||
sheet.setColumnWidth(1, 10 * 256); // 姓名/日期
|
|
||||||
sheet.setColumnWidth(2, 30 * 256); // 身份证号
|
|
||||||
|
|
||||||
// 日期列(每天一列)
|
List<SubConstructionUser> list = constructionUserService.list(Wrappers.lambdaQuery(SubConstructionUser.class)
|
||||||
for (int i = 3; i < 3 + daysInMonth; i++) {
|
.eq(SubConstructionUser::getProjectId, dto.getProjectId())
|
||||||
sheet.setColumnWidth(i, 5 * 256); // 每天列宽 15 字符
|
.isNotNull(SubConstructionUser::getEntryDate)
|
||||||
|
.and(wrapper -> wrapper.between(SubConstructionUser::getLeaveDate, startTime, endTime).or()
|
||||||
|
.isNull(SubConstructionUser::getLeaveDate))
|
||||||
|
.eq(dto.getTeamId() != null, SubConstructionUser::getTeamId, dto.getTeamId())
|
||||||
|
.eq(StrUtil.isNotBlank(dto.getTypeOfWork()), SubConstructionUser::getTypeOfWork, dto.getTypeOfWork())
|
||||||
|
.like(StringUtils.isNotBlank(dto.getUserName()), SubConstructionUser::getUserName, dto.getUserName())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (list.isEmpty()) {
|
||||||
|
throw new ServiceException("未查询到相关人员");
|
||||||
}
|
}
|
||||||
|
|
||||||
sheet.setColumnWidth(3 + daysInMonth, 5 * 256); // 合计
|
List<Long> userIds = list.stream().map(SubConstructionUser::getSysUserId).toList();
|
||||||
sheet.setColumnWidth(3 + daysInMonth + 1, 10 * 256); // 是否离场
|
|
||||||
sheet.setColumnWidth(3 + daysInMonth + 2, 15 * 256); // 签字
|
|
||||||
|
|
||||||
// ==================== 创建样式 ====================
|
int daysInMonth = yearMonth.lengthOfMonth(); // 动态获取天数
|
||||||
CellStyle borderStyle = createBorderStyle(workbook);
|
|
||||||
|
List<BusAttendance> attendanceList = list(Wrappers.lambdaQuery(BusAttendance.class)
|
||||||
|
.in(BusAttendance::getUserId, userIds)
|
||||||
|
.between(BusAttendance::getClockDate, start, end)
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<Long, List<SubConstructionUser>> teamUserMap = list.stream()
|
||||||
|
.collect(Collectors.groupingBy(user ->
|
||||||
|
user.getTeamId() != null ? user.getTeamId() : 0L));
|
||||||
|
|
||||||
|
Workbook workbook = new XSSFWorkbook();
|
||||||
|
|
||||||
|
for (Map.Entry<Long, List<SubConstructionUser>> entry : teamUserMap.entrySet()) {
|
||||||
|
Long teamId = entry.getKey();
|
||||||
|
System.out.println("teamId:" + teamId);
|
||||||
|
List<SubConstructionUser> users = entry.getValue();
|
||||||
|
SubConstructionUser constructionUser = users.getFirst();
|
||||||
|
String teamName = constructionUser.getTeamName();
|
||||||
|
if (teamId == 0) {
|
||||||
|
teamName = "无班组";
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(teamName)) {
|
||||||
|
teamName = teamId.toString();
|
||||||
|
}
|
||||||
|
System.out.println("name:" + teamName);
|
||||||
|
Sheet sheet = workbook.createSheet(teamName);
|
||||||
|
|
||||||
|
// ==================== 设置列宽 ====================
|
||||||
|
sheet.setColumnWidth(0, 5 * 256); // 序号
|
||||||
|
sheet.setColumnWidth(1, 10 * 256); // 姓名/日期
|
||||||
|
sheet.setColumnWidth(2, 30 * 256); // 身份证号
|
||||||
|
|
||||||
|
// 日期列(每天一列)
|
||||||
|
for (int i = 3; i < 3 + daysInMonth; i++) {
|
||||||
|
sheet.setColumnWidth(i, 5 * 256); // 每天列宽 15 字符
|
||||||
|
}
|
||||||
|
|
||||||
|
sheet.setColumnWidth(3 + daysInMonth, 5 * 256); // 合计
|
||||||
|
sheet.setColumnWidth(3 + daysInMonth + 1, 10 * 256); // 是否离场
|
||||||
|
sheet.setColumnWidth(3 + daysInMonth + 2, 15 * 256); // 签字
|
||||||
|
|
||||||
|
// ==================== 创建样式 ====================
|
||||||
|
CellStyle borderStyle = createBorderStyle(workbook);
|
||||||
// CellStyle numberStyle = createNumberStyle(workbook);
|
// CellStyle numberStyle = createNumberStyle(workbook);
|
||||||
|
|
||||||
|
|
||||||
// ==================== 表头部分 ====================
|
// ==================== 表头部分 ====================
|
||||||
Row titleRow = sheet.createRow(0);
|
Row titleRow = sheet.createRow(0);
|
||||||
Cell titleCell = titleRow.createCell(0);
|
Cell titleCell = titleRow.createCell(0);
|
||||||
titleCell.setCellValue("建筑施工企业现场人员考勤表(" + clockDate + ")");
|
titleCell.setCellValue("建筑施工企业现场人员考勤表(" + clockDate + ")");
|
||||||
titleCell.setCellStyle(createTitleCellStyle(workbook));
|
titleCell.setCellStyle(createTitleCellStyle(workbook));
|
||||||
|
|
||||||
// 合并标题行,横跨所有列
|
// 合并标题行,横跨所有列
|
||||||
int totalColumns = 3 + daysInMonth + 3; // 总列数
|
int totalColumns = 3 + daysInMonth + 3; // 总列数
|
||||||
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, totalColumns - 1));
|
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, totalColumns - 1));
|
||||||
|
|
||||||
// ==================== 第二行:项目名称和班组类别 ====================
|
// ==================== 第二行:项目名称和班组类别 ====================
|
||||||
Row projectRow = sheet.createRow(1);
|
Row projectRow = sheet.createRow(1);
|
||||||
|
|
||||||
// 设置项目名称(左对齐,占据前半部分)
|
// 设置项目名称(左对齐,占据前半部分)
|
||||||
Cell projectNameCell = projectRow.createCell(0);
|
Cell projectNameCell = projectRow.createCell(0);
|
||||||
projectNameCell.setCellValue("项目名称:" + project.getProjectName());
|
projectNameCell.setCellValue("项目名称:" + project.getProjectName());
|
||||||
projectNameCell.setCellStyle(createProjectCellStyle(workbook)); // 可选:设置样式
|
projectNameCell.setCellStyle(createProjectCellStyle(workbook)); // 可选:设置样式
|
||||||
|
|
||||||
int midColumn = (totalColumns / 2) - 1;
|
int midColumn = (totalColumns / 2) - 1;
|
||||||
|
|
||||||
// 设置班组类别(右对齐,占据后半部分)
|
// 设置班组类别(右对齐,占据后半部分)
|
||||||
int teamCol = midColumn + 1; //
|
int teamCol = midColumn + 1; //
|
||||||
Cell teamNameCell = projectRow.createCell(teamCol);
|
Cell teamNameCell = projectRow.createCell(teamCol);
|
||||||
teamNameCell.setCellValue("班组类别:" + teamName);
|
teamNameCell.setCellValue("班组类别:" + teamName);
|
||||||
teamNameCell.setCellStyle(createProjectCellStyle(workbook));
|
teamNameCell.setCellStyle(createProjectCellStyle(workbook));
|
||||||
|
|
||||||
// 合并项目名称区域(从第 0 列到中间)
|
// 合并项目名称区域(从第 0 列到中间)
|
||||||
if (midColumn >= 0) {
|
if (midColumn >= 0) {
|
||||||
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, midColumn));
|
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, midColumn));
|
||||||
}
|
|
||||||
|
|
||||||
// 合并班组类别区域(从 teamCol 开始,到最后一列)
|
|
||||||
if (teamCol < totalColumns) {
|
|
||||||
sheet.addMergedRegion(new CellRangeAddress(1, 1, teamCol, totalColumns - 1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 数据表头 ====================
|
|
||||||
Row headerRow = sheet.createRow(2);
|
|
||||||
writeHeaderRow(headerRow, daysInMonth);
|
|
||||||
// 设置表头边框
|
|
||||||
for (int i = 0; i < totalColumns; i++) {
|
|
||||||
Cell cell = headerRow.getCell(i);
|
|
||||||
if (cell != null) {
|
|
||||||
cell.setCellStyle(borderStyle);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 数据行 ====================
|
// 合并班组类别区域(从 teamCol 开始,到最后一列)
|
||||||
CellStyle numberBorderStyle = createNumberBorderStyle(workbook);
|
if (teamCol < totalColumns) {
|
||||||
|
sheet.addMergedRegion(new CellRangeAddress(1, 1, teamCol, totalColumns - 1));
|
||||||
|
}
|
||||||
|
|
||||||
int rowIndex = 3;
|
// ==================== 数据表头 ====================
|
||||||
for (SubConstructionUser user : users) {
|
Row headerRow = sheet.createRow(2);
|
||||||
Row row = sheet.createRow(rowIndex++);
|
writeHeaderRow(headerRow, daysInMonth);
|
||||||
writeDataRow(row, user, attendanceList, start, end, daysInMonth,borderStyle,numberBorderStyle);
|
// 设置表头边框
|
||||||
|
|
||||||
// 设置数据行边框
|
|
||||||
for (int i = 0; i < totalColumns; i++) {
|
for (int i = 0; i < totalColumns; i++) {
|
||||||
Cell cell = row.getCell(i);
|
Cell cell = headerRow.getCell(i);
|
||||||
if (cell != null) {
|
if (cell != null) {
|
||||||
cell.setCellStyle(borderStyle);
|
cell.setCellStyle(borderStyle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// ==================== 设置月份天数列为数字格式 + 边框 ====================
|
|
||||||
|
|
||||||
|
// ==================== 数据行 ====================
|
||||||
|
CellStyle numberBorderStyle = createNumberBorderStyle(workbook);
|
||||||
|
|
||||||
// 表头
|
int rowIndex = 3;
|
||||||
for (int i = 3; i < 3 + daysInMonth; i++) {
|
for (SubConstructionUser user : users) {
|
||||||
Cell headerCell = headerRow.getCell(i);
|
Row row = sheet.createRow(rowIndex++);
|
||||||
if (headerCell != null) {
|
writeDataRow(row, user, attendanceList, start, end, daysInMonth, borderStyle, numberBorderStyle);
|
||||||
headerCell.setCellStyle(numberBorderStyle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 数据行
|
// 设置数据行边框
|
||||||
for (int i = 3; i < 3 + daysInMonth; i++) {
|
for (int i = 0; i < totalColumns; i++) {
|
||||||
for (int j = 3; j < rowIndex; j++) {
|
Cell cell = row.getCell(i);
|
||||||
Row dataRow = sheet.getRow(j);
|
if (cell != null) {
|
||||||
if (dataRow != null) {
|
cell.setCellStyle(borderStyle);
|
||||||
Cell dataCell = dataRow.getCell(i);
|
|
||||||
if (dataCell != null) {
|
|
||||||
dataCell.setCellStyle(numberBorderStyle);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
// ==================== 设置月份天数列为数字格式 + 边框 ====================
|
||||||
// ==================== 设置合计列公式 ====================
|
|
||||||
|
|
||||||
|
// 表头
|
||||||
|
for (int i = 3; i < 3 + daysInMonth; i++) {
|
||||||
|
Cell headerCell = headerRow.getCell(i);
|
||||||
|
if (headerCell != null) {
|
||||||
|
headerCell.setCellStyle(numberBorderStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据行
|
||||||
|
for (int i = 3; i < 3 + daysInMonth; i++) {
|
||||||
|
for (int j = 3; j < rowIndex; j++) {
|
||||||
|
Row dataRow = sheet.getRow(j);
|
||||||
|
if (dataRow != null) {
|
||||||
|
Cell dataCell = dataRow.getCell(i);
|
||||||
|
if (dataCell != null) {
|
||||||
|
dataCell.setCellStyle(numberBorderStyle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ==================== 设置合计列公式 ====================
|
||||||
// for (int i = 4; i < rowIndex; i++) {
|
// for (int i = 4; i < rowIndex; i++) {
|
||||||
// Row dataRow = sheet.getRow(i);
|
// Row dataRow = sheet.getRow(i);
|
||||||
// if (dataRow != null) {
|
// if (dataRow != null) {
|
||||||
@ -1358,47 +1365,47 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// ==================== 表尾部分 ====================
|
// ==================== 表尾部分 ====================
|
||||||
|
|
||||||
sheet.createRow(rowIndex++);
|
sheet.createRow(rowIndex++);
|
||||||
|
|
||||||
Row footerRow1 = sheet.createRow(rowIndex++);
|
Row footerRow1 = sheet.createRow(rowIndex++);
|
||||||
int allTotalColumns = 3 + daysInMonth + 3;
|
int allTotalColumns = 3 + daysInMonth + 3;
|
||||||
int quarter = allTotalColumns / 4;
|
int quarter = allTotalColumns / 4;
|
||||||
|
|
||||||
int[] colSpans = {
|
int[] colSpans = {
|
||||||
quarter - 3, // 第一列减少3列
|
quarter - 3, // 第一列减少3列
|
||||||
quarter + 2, // 第二列增加2列
|
quarter + 2, // 第二列增加2列
|
||||||
quarter + 2, // 第三列增加2列
|
quarter + 2, // 第三列增加2列
|
||||||
allTotalColumns - (quarter - 3 + quarter + 2 + quarter + 2) // 自动计算最后一列
|
allTotalColumns - (quarter - 3 + quarter + 2 + quarter + 2) // 自动计算最后一列
|
||||||
};
|
};
|
||||||
|
|
||||||
int startCol = 0;
|
int startCol = 0;
|
||||||
String[] footerLabels = {"制表人:", "班组负责人:", "项目负责人:", "制表日期:"};
|
String[] footerLabels = {"制表人:", "班组负责人:", "项目负责人:", "制表日期:"};
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
int endCol = startCol + colSpans[i] - 1;
|
int endCol = startCol + colSpans[i] - 1;
|
||||||
|
|
||||||
// 合并单元格
|
// 合并单元格
|
||||||
sheet.addMergedRegion(new CellRangeAddress(rowIndex - 1, rowIndex - 1, startCol, endCol));
|
sheet.addMergedRegion(new CellRangeAddress(rowIndex - 1, rowIndex - 1, startCol, endCol));
|
||||||
|
|
||||||
// 创建单元格并设置内容
|
// 创建单元格并设置内容
|
||||||
Cell cell = footerRow1.createCell(startCol);
|
Cell cell = footerRow1.createCell(startCol);
|
||||||
cell.setCellValue(footerLabels[i]);
|
cell.setCellValue(footerLabels[i]);
|
||||||
cell.setCellStyle(createLeftAlignedCellStyle(workbook)); // 左对齐
|
cell.setCellStyle(createLeftAlignedCellStyle(workbook)); // 左对齐
|
||||||
|
|
||||||
|
startCol = endCol + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Row footerRow2 = sheet.createRow(rowIndex++);
|
||||||
|
footerRow2.createCell(0).setCellValue("注:");
|
||||||
|
footerRow2.createCell(1).setCellValue("1、本考勤表必须按照每日实际工时进行填写;");
|
||||||
|
Row footerRow3 = sheet.createRow(rowIndex++);
|
||||||
|
footerRow3.createCell(0).setCellValue("");
|
||||||
|
footerRow3.createCell(1).setCellValue("2、本考勤表作为工资计发的重要依据。");
|
||||||
|
|
||||||
startCol = endCol + 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row footerRow2 = sheet.createRow(rowIndex++);
|
|
||||||
footerRow2.createCell(0).setCellValue("注:");
|
|
||||||
footerRow2.createCell(1).setCellValue("1、本考勤表必须按照每日实际工时进行填写;");
|
|
||||||
Row footerRow3 = sheet.createRow(rowIndex++);
|
|
||||||
footerRow3.createCell(0).setCellValue("");
|
|
||||||
footerRow3.createCell(1).setCellValue("2、本考勤表作为工资计发的重要依据。");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
try (OutputStream outputStream = response.getOutputStream()) {
|
|
||||||
workbook.write(outputStream);
|
workbook.write(outputStream);
|
||||||
workbook.close();
|
workbook.close();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@ -1417,6 +1424,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
}
|
}
|
||||||
return columnName.toString();
|
return columnName.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private CellStyle createBorderStyle(Workbook workbook) {
|
private CellStyle createBorderStyle(Workbook workbook) {
|
||||||
CellStyle style = workbook.createCellStyle();
|
CellStyle style = workbook.createCellStyle();
|
||||||
style.setBorderTop(BorderStyle.THIN);
|
style.setBorderTop(BorderStyle.THIN);
|
||||||
@ -1425,10 +1433,11 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
style.setBorderRight(BorderStyle.THIN);
|
style.setBorderRight(BorderStyle.THIN);
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CellStyle createNumberBorderStyle(Workbook workbook) {
|
private CellStyle createNumberBorderStyle(Workbook workbook) {
|
||||||
CellStyle style = workbook.createCellStyle();
|
CellStyle style = workbook.createCellStyle();
|
||||||
DataFormat format = workbook.createDataFormat();
|
DataFormat format = workbook.createDataFormat();
|
||||||
style.setDataFormat(format.getFormat("0")); // 强制显示 0
|
style.setDataFormat(format.getFormat("0.0")); // 显示一位小数
|
||||||
style.setBorderTop(BorderStyle.THIN);
|
style.setBorderTop(BorderStyle.THIN);
|
||||||
style.setBorderBottom(BorderStyle.THIN);
|
style.setBorderBottom(BorderStyle.THIN);
|
||||||
style.setBorderLeft(BorderStyle.THIN);
|
style.setBorderLeft(BorderStyle.THIN);
|
||||||
@ -1478,8 +1487,8 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
row.createCell(2 + daysInMonth + 3).setCellValue("签字");
|
row.createCell(2 + daysInMonth + 3).setCellValue("签字");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeDataRow(Row row, SubConstructionUser user, List<BusAttendance> attendanceList, LocalDate start, LocalDate end, int daysInMonth,CellStyle borderStyle,CellStyle numberBorderStyle) {
|
private void writeDataRow(Row row, SubConstructionUser user, List<BusAttendance> attendanceList, LocalDate start, LocalDate end, int daysInMonth, CellStyle borderStyle, CellStyle numberBorderStyle) {
|
||||||
int index = row.getRowNum()-2;
|
int index = row.getRowNum() - 2;
|
||||||
row.createCell(0).setCellValue(index);
|
row.createCell(0).setCellValue(index);
|
||||||
row.createCell(1).setCellValue(user.getUserName());
|
row.createCell(1).setCellValue(user.getUserName());
|
||||||
row.createCell(2).setCellValue(idCardEncryptorUtil.decrypt(user.getSfzNumber()));
|
row.createCell(2).setCellValue(idCardEncryptorUtil.decrypt(user.getSfzNumber()));
|
||||||
@ -1502,7 +1511,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
sumCell.setCellFormula(formula);
|
sumCell.setCellFormula(formula);
|
||||||
sumCell.setCellStyle(borderStyle);
|
sumCell.setCellStyle(borderStyle);
|
||||||
|
|
||||||
String leaveStatus = "0".equals(user.getExitStatus()) ? "未离场" : "已离场";
|
String leaveStatus = user.getLeaveDate() != null ? "已离场" : "未离场";
|
||||||
row.createCell(2 + daysInMonth + 2).setCellValue(leaveStatus);
|
row.createCell(2 + daysInMonth + 2).setCellValue(leaveStatus);
|
||||||
|
|
||||||
row.createCell(2 + daysInMonth + 3).setCellValue("");
|
row.createCell(2 + daysInMonth + 3).setCellValue("");
|
||||||
@ -1541,7 +1550,7 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
|
|||||||
|
|
||||||
while (!current.isAfter(end)) {
|
while (!current.isAfter(end)) {
|
||||||
Double value = getAttendanceValue(userId, current, attendanceList);
|
Double value = getAttendanceValue(userId, current, attendanceList);
|
||||||
total+=value;
|
total += value;
|
||||||
current = current.plusDays(1);
|
current = current.plusDays(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user