From 8cbb4e01b9226b32797a48489c5e1b30da3e2110 Mon Sep 17 00:00:00 2001
From: zouyu <2723363702@qq.com>
Date: 星期一, 13 四月 2026 15:01:32 +0800
Subject: [PATCH] 数采调整&人员考勤导出开发
---
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/StaffAttendanceTrackingRecordServiceImpl.java | 279 +++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 258 insertions(+), 21 deletions(-)
diff --git a/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/StaffAttendanceTrackingRecordServiceImpl.java b/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/StaffAttendanceTrackingRecordServiceImpl.java
index 27a6c78..b46865f 100644
--- a/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/StaffAttendanceTrackingRecordServiceImpl.java
+++ b/inspect-server/src/main/java/com/ruoyi/inspect/service/impl/StaffAttendanceTrackingRecordServiceImpl.java
@@ -2,12 +2,14 @@
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.support.ExcelTypeEnum;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.ruoyi.common.core.domain.Result;
+import com.google.common.util.concurrent.AtomicDouble;
import com.ruoyi.common.enums.ClockInState;
import com.ruoyi.common.enums.EnterOrExitType;
import com.ruoyi.common.enums.SyncStatus;
@@ -15,13 +17,15 @@
import com.ruoyi.common.utils.api.icc.model.GetResultPageRequest;
import com.ruoyi.common.utils.api.icc.model.GetResultPageResponse;
import com.ruoyi.inspect.dto.StaffAttendanceDTO;
+import com.ruoyi.inspect.excel.StaffAttendanceAnnotationTextExcelData;
+import com.ruoyi.inspect.excel.StaffAttendanceExcelData;
+import com.ruoyi.inspect.excel.handler.CommentWriteHandler;
import com.ruoyi.inspect.mapper.StaffAttendanceTrackingRecordMapper;
import com.ruoyi.inspect.pojo.StaffAttendanceTrackingRecord;
import com.ruoyi.inspect.service.StaffAttendanceTrackingRecordService;
import com.ruoyi.inspect.util.HourDiffCalculator;
import com.ruoyi.inspect.util.TimeDiffCalculator;
import com.ruoyi.inspect.vo.StaffAttendanceVO;
-import com.ruoyi.inspect.vo.StaffClockInVO;
import com.ruoyi.performance.dto.PerformanceShiftMapDto;
import com.ruoyi.performance.mapper.PerformanceShiftMapper;
import com.ruoyi.performance.mapper.ShiftTimeMapper;
@@ -29,17 +33,27 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.poi.ss.usermodel.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
+import javax.servlet.http.HttpServletResponse;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
/**
@@ -75,6 +89,14 @@
// 鏁板瓧閮ㄥ垎鍥哄畾闀垮害
private static final int DIGIT_LENGTH = 6;
+
+ private static final String holidayLeaveKeyword = "浼�";//浼戝亣锛岃皟浼戝亣鐝鍏抽敭瀛�
+ private static final String personalLeaveKeyword = "浜�";//浜嬪亣鐝鍏抽敭瀛�
+ private static final String annualLeaveKeyword = "骞�";//骞村亣鐝鍏抽敭瀛�
+ private static final String officialTripKeyword = "鍏�";//鍏樊鐝鍏抽敭瀛�
+ private static final String marriageLeaveKeyword = "濠�";//濠氬亣鐝鍏抽敭瀛�
+ private static final String bereavementLeaveKeyword = "涓�";//涓у亣鐝鍏抽敭瀛�
+ private static final String sickLeaveKeyword = "鐥�";//鐥呭亣鐝鍏抽敭瀛�
/**
* 鍚屾鐨勯棬绂佽澶囧垪琛�
@@ -142,9 +164,13 @@
return true;
}
- @Override
- public IPage<StaffAttendanceVO> pageAttendanceRecord(Page<StaffAttendanceTrackingRecord> page,
- StaffAttendanceDTO staffAttendanceDTO) {
+ /**
+ * 鏌ヨ鑰冨嫟璁板綍
+ * @param performanceShifts 鐝淇℃伅
+ * @param staffAttendanceDTO 鑰冨嫟鏌ヨ鏉′欢
+ * @return
+ */
+ public List<StaffAttendanceVO> getAttendanceRecord(List<PerformanceShiftMapDto> performanceShifts,StaffAttendanceDTO staffAttendanceDTO) {
// 鏌ヨ鎵撳崱璁板綍
Wrapper<StaffAttendanceTrackingRecord> queryWrapper = Wrappers.<StaffAttendanceTrackingRecord>lambdaQuery()
.eq(StaffAttendanceTrackingRecord::getEnableReport, Boolean.TRUE)
@@ -155,9 +181,6 @@
.or()
.like(StaffAttendanceTrackingRecord::getPersonName, staffAttendanceDTO.getKeyword()));
List<StaffAttendanceTrackingRecord> recordList = baseMapper.selectList(queryWrapper);
- // 鏌ヨ鐝
- List<PerformanceShiftMapDto> performanceShifts = performanceShiftMapper.selectListByWorkTime(
- staffAttendanceDTO.getStartDate(), staffAttendanceDTO.getEndDate(), staffAttendanceDTO.getKeyword());
// 缁勮鏁版嵁
List<StaffAttendanceVO> resultList = new ArrayList<>();
for (int i = 0; i < performanceShifts.size(); i++) {
@@ -185,8 +208,7 @@
LocalDateTime currentShiftStartDateTime = LocalDateTime.of(p.getWorkTime().toLocalDate(),
currentShiftStartTime);
// 涓嬩竴鐝寮�濮嬫椂闂�
- LocalDateTime nextShiftStartDateTime = getShiftStartDateTime(i + 1, performanceShifts,
- startDateTime.plusDays(1L));
+ LocalDateTime nextShiftStartDateTime = getShiftStartDateTime(p.getPersonCode(), performanceShifts, startDateTime.plusDays(1L));
if (Double.compare(hourDiff, 0) == -1) {
// 濡傛灉灏忔椂宸负璐熸暟锛岃〃绀鸿法澶╋紝缁撴潫鏃堕棿闇�鍔犱竴
endDateTime = endDateTime.plusDays(1L);
@@ -258,6 +280,7 @@
vo.setPersonName(p.getUserName());
//搴斿嫟鏃堕暱
double plannedWorkHours = Math.abs(hourDiff);
+ vo.setDiffHour(hourDiff);
vo.setPlannedWorkHours(plannedWorkHours);
vo.setSwingDate(startDateTime);
vo.setWorkDateTime(workDateTime);
@@ -289,7 +312,16 @@
resultList.add(vo);
}
}
- return limitPages(page, resultList);
+ return resultList;
+ }
+
+ @Override
+ public IPage<StaffAttendanceVO> pageAttendanceRecord(Page<StaffAttendanceTrackingRecord> page,
+ StaffAttendanceDTO staffAttendanceDTO) {
+ // 鏌ヨ鐝
+ List<PerformanceShiftMapDto> performanceShifts = performanceShiftMapper.selectListByWorkTime(
+ staffAttendanceDTO.getStartDate(), staffAttendanceDTO.getEndDate(), staffAttendanceDTO.getKeyword());
+ return limitPages(page, getAttendanceRecord(performanceShifts,staffAttendanceDTO));
}
@Override
@@ -369,6 +401,208 @@
return this.saveOrUpdateBatch(records);
}
+ @Override
+ public void exportStaffAttendanceRecords(HttpServletResponse response, StaffAttendanceDTO staffAttendanceDTO) {
+ response.reset();
+ try{
+ List<LocalDate> attendanceDateList = buildAttendanceDateList(staffAttendanceDTO);
+ //鎵规敞淇℃伅鍧愭爣淇℃伅
+ List<StaffAttendanceAnnotationTextExcelData> annotationTextList = new ArrayList<>();
+ // 鏌ヨ鐝
+ List<PerformanceShiftMapDto> performanceShifts = performanceShiftMapper.selectListByWorkTime(
+ staffAttendanceDTO.getStartDate(), staffAttendanceDTO.getEndDate(), staffAttendanceDTO.getKeyword());
+ //鑾峰彇鑰冨嫟鏁版嵁
+ List<StaffAttendanceVO> attendanceRecords = getAttendanceRecord(performanceShifts,staffAttendanceDTO);
+ //缁勮瀵煎嚭鏁版嵁
+ List<StaffAttendanceExcelData> excelData = new ArrayList<>();
+ Map<Integer, List<PerformanceShiftMapDto>> groupByUserId = performanceShifts.stream().collect(Collectors.groupingBy(PerformanceShiftMapDto::getUserId));
+ List<Integer> userIdKeys = groupByUserId.keySet().stream().sorted().collect(Collectors.toList());
+ for (int i = 0; i < userIdKeys.size(); i++) {
+ List<PerformanceShiftMapDto> shiftMapDtos = groupByUserId.get(userIdKeys.get(i));
+ StaffAttendanceExcelData attendanceExcelData = new StaffAttendanceExcelData();
+ attendanceExcelData.setExcelIndex(i+1);
+ List<String> shiftList = new ArrayList<>();
+ attendanceExcelData.setPersonName(shiftMapDtos.get(0).getUserName());
+ AtomicInteger holidayCount = new AtomicInteger(0);//浼戞伅澶╂暟
+ AtomicInteger personalLeaveCount = new AtomicInteger(0);//浜嬪亣澶╂暟
+ AtomicInteger annualLeaveCount = new AtomicInteger(0);//骞村亣澶╂暟
+ AtomicInteger officialTripCount = new AtomicInteger(0);//鍏樊澶╂暟
+ AtomicInteger marriageLeaveCount = new AtomicInteger(0);//濠氬亣澶╂暟
+ AtomicInteger bereavementLeaveCount = new AtomicInteger(0);//涓у亣澶╂暟
+ AtomicInteger sickLeaveCount = new AtomicInteger(0);//鐥呭亣澶╂暟
+ AtomicInteger attendanceDayCount = new AtomicInteger(0);//鍑哄嫟澶╂暟
+ AtomicDouble attendanceWorkHourCount = new AtomicDouble(0D);//鍑哄嫟鎬绘椂闂�
+ for (int j = 0; j < shiftMapDtos.size(); j++) {
+ PerformanceShiftMapDto shiftMapDto = shiftMapDtos.get(j);
+ //缁熻鍚勫亣鏈熷拰鍏樊鐨勫ぉ鏁�
+ if(StringUtils.contains(shiftMapDto.getShiftName(),holidayLeaveKeyword)){
+ holidayCount.getAndIncrement();
+ }else if(StringUtils.contains(shiftMapDto.getShiftName(),personalLeaveKeyword)){
+ personalLeaveCount.getAndIncrement();
+ }else if(StringUtils.contains(shiftMapDto.getShiftName(),annualLeaveKeyword)){
+ annualLeaveCount.getAndIncrement();
+ }else if(StringUtils.contains(shiftMapDto.getShiftName(),officialTripKeyword)){
+ officialTripCount.getAndIncrement();
+ }else if(StringUtils.contains(shiftMapDto.getShiftName(),marriageLeaveKeyword)){
+ marriageLeaveCount.getAndIncrement();
+ }else if(StringUtils.contains(shiftMapDto.getShiftName(),bereavementLeaveKeyword)){
+ bereavementLeaveCount.getAndIncrement();
+ }else if(StringUtils.contains(shiftMapDto.getShiftName(),sickLeaveKeyword)){
+ sickLeaveCount.getAndIncrement();
+ }
+ if(StringUtils.isAllBlank(shiftMapDto.getStartTime(),shiftMapDto.getEndTime())){
+ shiftList.add(shiftMapDto.getShiftName());
+ }else{
+ //杩囨护褰撳墠浜哄憳鐨勭彮娆′俊鎭�
+ StaffAttendanceVO vo = attendanceRecords.stream().filter(f->StringUtils.isNotBlank(f.getPersonCode())).filter(f -> StringUtils.equals(f.getPersonCode(), shiftMapDto.getPersonCode()) && f.getSwingDate().isEqual(shiftMapDto.getWorkTime())).findFirst().orElse(null);
+ if(ObjectUtils.isEmpty(vo)){
+ shiftList.add("");
+ }else{
+ String actualWorkHours = Objects.toString(vo.getActualWorkHours(), "");
+ Double diffHour = ObjectUtils.defaultIfNull(vo.getDiffHour(), 0D);
+ if (StringUtils.isBlank(actualWorkHours)) {
+ shiftList.add("");
+ } else {
+ shiftList.add(Double.compare(diffHour, 0D) < 0 ? "-" + actualWorkHours : actualWorkHours);
+ attendanceDayCount.getAndIncrement();
+ attendanceWorkHourCount.getAndAdd(Double.parseDouble(actualWorkHours));
+ }
+ }
+ }
+ if(StringUtils.isNotBlank(shiftMapDto.getAnnotationText())){
+ annotationTextList.add(new StaffAttendanceAnnotationTextExcelData(i,j,shiftMapDto.getAnnotationText()));
+ }
+ }
+ attendanceExcelData.setShiftList(shiftList);
+ attendanceExcelData.setAttendanceDayCount(attendanceDayCount.get());
+ attendanceExcelData.setAttendanceWorkHourCount(attendanceWorkHourCount.get());
+ //鐝鑰冨嫟澶╂暟
+ attendanceExcelData.setHolidayCount(holidayCount.get());
+ attendanceExcelData.setPersonalLeaveCount(personalLeaveCount.get());
+ attendanceExcelData.setAnnualLeaveCount(annualLeaveCount.get());
+ attendanceExcelData.setOfficialTripCount(officialTripCount.get());
+ attendanceExcelData.setMarriageLeaveCount(marriageLeaveCount.get());
+ attendanceExcelData.setBereavementLeaveCount(bereavementLeaveCount.get());
+ attendanceExcelData.setSickLeaveCount(sickLeaveCount.get());
+ excelData.add(attendanceExcelData);
+ }
+ //瀵煎嚭
+ String fileName = "涓ぉ鑰愪笣璐ㄩ噺鑰冨嫟姹囨��"+ ExcelTypeEnum.XLSX;
+ fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.toString());
+ response.setContentType("application/vnd.ms-excel");
+ response.setHeader("Cache-Control", "no-cache");
+ response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
+ response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
+ InputStream resourceAsStream = buildAttendanceTemplate(attendanceDateList);
+ EasyExcel.write(response.getOutputStream())
+ .withTemplate(resourceAsStream)
+ .registerWriteHandler(new CommentWriteHandler(excelData, annotationTextList))
+ .relativeHeadRowIndex(4)
+ .sheet()
+ .doFill(excelData);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private List<LocalDate> buildAttendanceDateList(StaffAttendanceDTO staffAttendanceDTO) {
+ if (staffAttendanceDTO == null || staffAttendanceDTO.getStartDate() == null || staffAttendanceDTO.getEndDate() == null) {
+ throw new IllegalArgumentException("瀵煎嚭鏃堕棿鑼冨洿涓嶈兘涓虹┖");
+ }
+ LocalDate startDate = staffAttendanceDTO.getStartDate().toLocalDate();
+ LocalDate endDate = staffAttendanceDTO.getEndDate().toLocalDate();
+ if (startDate.isAfter(endDate)) {
+ throw new IllegalArgumentException("寮�濮嬫椂闂翠笉鑳芥櫄浜庣粨鏉熸椂闂�");
+ }
+ List<LocalDate> attendanceDateList = new ArrayList<>();
+ for (LocalDate currentDate = startDate; !currentDate.isAfter(endDate); currentDate = currentDate.plusDays(1)) {
+ attendanceDateList.add(currentDate);
+ }
+ if (attendanceDateList.size() > 31) {
+ throw new IllegalArgumentException("瀵煎嚭鏃堕棿鑼冨洿涓嶈兘瓒呰繃31澶�");
+ }
+ return attendanceDateList;
+ }
+
+ private InputStream buildAttendanceTemplate(List<LocalDate> attendanceDateList) throws IOException {
+ try (InputStream templateStream = this.getClass().getResourceAsStream("/static/staff_attendance_template.xlsx");
+ Workbook workbook = WorkbookFactory.create(templateStream);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
+ Sheet sheet = workbook.getSheetAt(0);
+ fillAttendanceHeader(sheet, attendanceDateList);
+ workbook.write(outputStream);
+ return new ByteArrayInputStream(outputStream.toByteArray());
+ } catch (Exception e) {
+ throw new IOException("鏋勫缓鑰冨嫟瀵煎嚭妯℃澘澶辫触", e);
+ }
+ }
+
+ private void fillAttendanceHeader(Sheet sheet, List<LocalDate> attendanceDateList) {
+ if (sheet == null || attendanceDateList == null || attendanceDateList.isEmpty()) {
+ return;
+ }
+ Row titleRow = sheet.getRow(1);
+ if (titleRow != null) {
+ Cell titleCell = titleRow.getCell(0);
+ if (titleCell != null) {
+ titleCell.setCellValue(attendanceDateList.get(attendanceDateList.size() - 1).format(DateTimeFormatter.ofPattern("yyyy骞碝鏈�")));
+ }
+ }
+ Row weekRow = sheet.getRow(2);
+ Row dayRow = sheet.getRow(3);
+ if (weekRow == null || dayRow == null) {
+ return;
+ }
+ final int startColumnIndex = 2;
+ final int maxDateColumnCount = 31;
+ for (int i = 0; i < maxDateColumnCount; i++) {
+ Cell weekCell = getOrCreateCell(weekRow, startColumnIndex + i, startColumnIndex);
+ Cell dayCell = getOrCreateCell(dayRow, startColumnIndex + i, startColumnIndex);
+ if (i < attendanceDateList.size()) {
+ LocalDate currentDate = attendanceDateList.get(i);
+ weekCell.setCellValue(resolveWeekOfYear(currentDate));
+ dayCell.setCellValue(currentDate.getDayOfMonth());
+ } else {
+ weekCell.setBlank();
+ dayCell.setBlank();
+ }
+ }
+ }
+
+ private Cell getOrCreateCell(Row row, int cellIndex, int templateCellIndex) {
+ Cell cell = row.getCell(cellIndex);
+ if (cell != null) {
+ return cell;
+ }
+ Cell templateCell = row.getCell(templateCellIndex);
+ cell = row.createCell(cellIndex);
+ if (templateCell != null && templateCell.getCellStyle() != null) {
+ cell.setCellStyle(templateCell.getCellStyle());
+ }
+ return cell;
+ }
+
+ private String resolveWeekOfYear(LocalDate date) {
+ switch (date.getDayOfWeek()) {
+ case MONDAY:
+ return "涓�";
+ case TUESDAY:
+ return "浜�";
+ case WEDNESDAY:
+ return "涓�";
+ case THURSDAY:
+ return "鍥�";
+ case FRIDAY:
+ return "浜�";
+ case SATURDAY:
+ return "鍏�";
+ case SUNDAY:
+ return "鏃�";
+ default:
+ return "";
+ }
+ }
+
/**
* 鑷畾涔夊垎椤垫柟娉�
*
@@ -416,20 +650,23 @@
}
/**
- * 鑾峰彇鎸囧畾涓嬫爣鐨勭彮娆″紑濮嬫椂闂�
- *
- * @param index
- * @param dtoList
+ * 鑾峰彇褰撳墠鐝鏃ユ湡鐨勪笅涓�鐝寮�濮嬫椂闂�
+ * @param personCode 浜哄憳缂栧彿
+ * @param dtoList 鐝鍒楄〃
+ * @param nextShiftTime 涓嬩竴鐝鏃堕棿
* @return
*/
- private LocalDateTime getShiftStartDateTime(int index, List<PerformanceShiftMapDto> dtoList,
- LocalDateTime nextShiftTime) {
- if (dtoList.isEmpty() || index >= dtoList.size()) {
+ private LocalDateTime getShiftStartDateTime(String personCode,List<PerformanceShiftMapDto> dtoList, LocalDateTime nextShiftTime) {
+ if (dtoList.isEmpty()) {
return LocalDateTime.of(nextShiftTime.toLocalDate(), LocalTime.MAX);
}
- LocalTime localTime = ObjectUtil.isNull(dtoList.get(index).getStartTime()) ? LocalTime.MAX
- : LocalTime.parse(dtoList.get(index).getStartTime(), HHmm);
- return LocalDateTime.of(nextShiftTime.toLocalDate(), localTime);
+ //杩囨护褰撳墠浜哄憳鐨勪笅涓�鐝淇℃伅
+ PerformanceShiftMapDto shiftMapDto = dtoList.stream().filter(f -> StringUtils.equals(f.getPersonCode(), personCode) && f.getWorkTime().isEqual(nextShiftTime)).findFirst().orElse(new PerformanceShiftMapDto());
+ if(StringUtils.isEmpty(shiftMapDto.getStartTime())){
+ return LocalDateTime.of(nextShiftTime.toLocalDate(), LocalTime.MAX);
+ }
+ LocalTime nextShiftStartTime = LocalTime.parse(shiftMapDto.getStartTime(), HHmm);
+ return LocalDateTime.of(nextShiftTime.toLocalDate(), nextShiftStartTime);
}
/**
--
Gitblit v1.9.3