zouyu
2026-04-13 8cbb4e01b9226b32797a48489c5e1b30da3e2110
数采调整&人员考勤导出开发
已添加5个文件
已修改16个文件
682 ■■■■■ 文件已修改
cnas-device/src/main/java/com/ruoyi/device/controller/DeviceController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/dto/DeviceCollectionDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/utils/DataAcquisition.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/controller/StaffAttendanceController.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/excel/StaffAttendanceAnnotationTextExcelData.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/excel/StaffAttendanceExcelData.java 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/excel/handler/CommentWriteHandler.java 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/excel/handler/StaffAttendanceHeaderWriteHandler.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsOrderTemplateMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrder.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/StaffAttendanceTrackingRecordService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/RawMaterialOrderServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/StaffAttendanceTrackingRecordServiceImpl.java 279 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/task/SyncStaffAttendanceRecordSchedule.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/vo/InsOrderPrintingVo.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/java/com/ruoyi/inspect/vo/StaffAttendanceVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/mapper/InsOrderMapper.xml 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
inspect-server/src/main/resources/static/staff_attendance_template.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
performance-server/src/main/java/com/ruoyi/performance/dto/PerformanceShiftMapDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
performance-server/src/main/resources/mapper/PerformanceShiftMapper.xml 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
cnas-device/src/main/java/com/ruoyi/device/controller/DeviceController.java
@@ -147,7 +147,6 @@
        return Result.success(deviceService.selectDeviceByCode(id));
    }
    @ApiOperation("/数采-数据采集")
    @PostMapping("/dataCollection")
    public Result<?> dataAcquisition(HttpServletRequest request,@RequestBody DeviceCollectionDto dto) {
cnas-device/src/main/java/com/ruoyi/device/dto/DeviceCollectionDto.java
@@ -39,4 +39,7 @@
    @ApiModelProperty(value = "批次号")
    private String lotBatchNo;
}
cnas-device/src/main/java/com/ruoyi/device/service/impl/DeviceServiceImpl.java
@@ -237,11 +237,13 @@
        // æ•°é‡‡è¿”回信息
        Map<String, Object> map = new HashMap<>();
        for (Device device : deviceList) {
            Device device1 = deviceMapper.selectById(device.getId());
            dto.setDbUserName(device1.getDbUserName());
            dto.setDbPassword(device1.getDbPassword());
            dto.setDbTable(device1.getDbTable());
            //NS-YL3141:哑铃片全自动检测实验室
            if(!StringUtils.equals(device1.getManagementNumber(),"NS-YL3141")){
                dto.setDbTable(device1.getDbTable());
            }
            String ip = device.getIp();
            // æ ¹æ®æ£€éªŒé¡¹èŽ·å–config
cnas-device/src/main/java/com/ruoyi/device/utils/DataAcquisition.java
@@ -82,6 +82,8 @@
        String mdbEntrustCode = StringUtils.isNotBlank(device.getEntrustCode())?device.getEntrustCode():"";
        String mdbSampleCode = StringUtils.isNotBlank(device.getSampleCode())?device.getSampleCode():"";
        String dbFileName = StringUtils.isNotBlank(device.getDbFileName())?device.getDbFileName():"";
        String colorTag = StringUtils.isNotBlank(cableTag)?cableTag:"";
        String lotBatchNoStr = StringUtils.isNotBlank(lotBatchNo)?lotBatchNo:"";
        String http = HTTP + ip + GETFILE +
                "?filePath=" + device.getCollectUrl() +
                "&fileExtension=" + device.getFileType() +
@@ -92,8 +94,8 @@
                "&dbFileName=" + dbFileName +
                "&dbUserName=" + dbUserName +
                "&dbPassword=" + dbPassword +
                "&lotBatchNo=" + lotBatchNo +
                "&cableTag=" + cableTag +
                "&lotBatchNo=" + lotBatchNoStr +
                "&cableTag=" + colorTag +
                "&dbTable=" + dbTable;
        System.out.println("请求的 URL: " + http);
        String result = null;
inspect-server/src/main/java/com/ruoyi/inspect/controller/StaffAttendanceController.java
@@ -9,6 +9,7 @@
import com.ruoyi.inspect.dto.StaffAttendanceDTO;
import com.ruoyi.inspect.pojo.StaffAttendanceTrackingRecord;
import com.ruoyi.inspect.service.StaffAttendanceTrackingRecordService;
import com.ruoyi.performance.dto.AuxiliaryOriginalHoursLookDto;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.ObjectUtils;
@@ -16,6 +17,7 @@
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.Arrays;
@@ -78,4 +80,11 @@
        return Result.success(trackingRecordService.updateById(trackingRecord));
    }
    @ApiOperation("导出考勤记录")
    @GetMapping("/exportStaffAttendanceRecords")
    public void exportStaffAttendanceRecords(HttpServletResponse response, StaffAttendanceDTO staffAttendanceDTO){
        trackingRecordService.exportStaffAttendanceRecords(response,staffAttendanceDTO);
    }
}
inspect-server/src/main/java/com/ruoyi/inspect/excel/StaffAttendanceAnnotationTextExcelData.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.inspect.excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
 * äººå‘˜è€ƒå‹¤å¯¼å‡ºï¼Œæ‰¹æ³¨ä¸‹æ ‡å¯¹è±¡
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class StaffAttendanceAnnotationTextExcelData {
    /**
     * åˆ—下标
     */
    @ApiModelProperty("列下标")
    private int rowIndex;
    /**
     * åˆ—下标
     */
    @ApiModelProperty("列下标")
    private int cellIndex;
    /**
     * æ‰¹æ³¨æ–‡æœ¬
     */
    @ApiModelProperty("批注文本")
    private String annotationText;
}
inspect-server/src/main/java/com/ruoyi/inspect/excel/StaffAttendanceExcelData.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,88 @@
package com.ruoyi.inspect.excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
 * äººå‘˜è€ƒå‹¤è®°å½•导出封装对象
 */
@Data
public class StaffAttendanceExcelData {
    /**
     * åºå·
     */
    @ApiModelProperty("序号")
    private Integer excelIndex;
    /**
     * å§“名
     */
    @ApiModelProperty("姓名")
    private String personName;
    /**
     * ç­æ¬¡è€ƒå‹¤åˆ—表
     */
    @ApiModelProperty("班次考勤列表")
    private List<String> shiftList;
    /**
     * ä¼‘息天数
     */
    @ApiModelProperty("休息天数")
    private Integer holidayCount;
    /**
     * äº‹å‡å¤©æ•°
     */
    @ApiModelProperty("事假天数")
    private Integer personalLeaveCount;
    /**
     * å¹´å‡å¤©æ•°
     */
    @ApiModelProperty("年假天数")
    private Integer annualLeaveCount;
    /**
     * å…¬å·®å¤©æ•°
     */
    @ApiModelProperty("公差天数")
    private Integer officialTripCount;
    /**
     * å©šå‡å¤©æ•°
     */
    @ApiModelProperty("婚假天数")
    private Integer marriageLeaveCount;
    /**
     * ä¸§å‡å¤©æ•°
     */
    @ApiModelProperty("丧假天数")
    private Integer bereavementLeaveCount;
    /**
     * ç—…假天数
     */
    @ApiModelProperty("病假天数")
    private Integer sickLeaveCount;
    /**
     * å‡ºå‹¤å¤©æ•°
     */
    @ApiModelProperty("出勤天数")
    private Integer attendanceDayCount;
    /**
     * å‡ºå‹¤æ€»æ—¶é—´
     */
    @ApiModelProperty("出勤总时间")
    private Double attendanceWorkHourCount;
}
inspect-server/src/main/java/com/ruoyi/inspect/excel/handler/CommentWriteHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,112 @@
package com.ruoyi.inspect.excel.handler;
import com.alibaba.excel.util.BooleanUtils;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.handler.context.RowWriteHandlerContext;
import com.ruoyi.inspect.excel.StaffAttendanceAnnotationTextExcelData;
import com.ruoyi.inspect.excel.StaffAttendanceExcelData;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Comment;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFClientAnchor;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * äººå‘˜è€ƒå‹¤å¯¼å‡ºå†™å…¥å¤„理器
 * 1. è¡¥é½æ¨¡æ¿ä¸­ä¸å­˜åœ¨çš„班次明细单元格
 * 2. åœ¨å¯¹åº”单元格上写入批注
 */
public class CommentWriteHandler implements RowWriteHandler {
    /**
     * æ¨¡æ¿æ•°æ®èµ·å§‹è¡Œä¸ºç¬¬ 5 è¡Œ
     */
    private static final int DATA_START_ROW_INDEX = 4;
    /**
     * æ¨¡æ¿ç­æ¬¡èµ·å§‹åˆ—为 C åˆ—
     */
    private static final int DATA_START_COLUMN_INDEX = 2;
    private final List<StaffAttendanceExcelData> excelDataList;
    private final Map<String, String> annotationTextMap = new HashMap<>();
    public CommentWriteHandler(List<StaffAttendanceExcelData> excelDataList,
            List<StaffAttendanceAnnotationTextExcelData> annotationTextList) {
        this.excelDataList = excelDataList == null ? Collections.emptyList() : excelDataList;
        if (annotationTextList == null) {
            return;
        }
        annotationTextList.stream()
                .filter(item -> item != null && StringUtils.isNotBlank(item.getAnnotationText()))
                .forEach(item -> annotationTextMap.put(buildKey(item.getRowIndex(), item.getCellIndex()),
                        item.getAnnotationText()));
    }
    @Override
    public void afterRowDispose(RowWriteHandlerContext context) {
        if (BooleanUtils.isTrue(context.getHead())) {
            return;
        }
        Row row = context.getRow();
        if (row == null || row.getRowNum() < DATA_START_ROW_INDEX) {
            return;
        }
        int dataIndex = row.getRowNum() - DATA_START_ROW_INDEX;
        if (dataIndex < 0 || dataIndex >= excelDataList.size()) {
            return;
        }
        StaffAttendanceExcelData excelData = excelDataList.get(dataIndex);
        if (excelData == null || excelData.getShiftList() == null) {
            return;
        }
        Sheet sheet = row.getSheet();
        CellStyle cellStyle = sheet.getColumnStyle(DATA_START_COLUMN_INDEX);
        Drawing<?> drawingPatriarch = null;
        List<String> shiftList = excelData.getShiftList();
        for (int i = 0; i < shiftList.size(); i++) {
            int cellIndex = DATA_START_COLUMN_INDEX + i;
            Cell cell = row.getCell(cellIndex);
            if (cell == null) {
                cell = row.createCell(cellIndex);
            }
            if (cellStyle != null) {
                cell.setCellStyle(cellStyle);
            }
            String cellValue = shiftList.get(i);
            if (StringUtils.isNotBlank(cellValue)) {
                cell.setCellValue(cellValue);
            }
            String annotationText = annotationTextMap.get(buildKey(dataIndex, i));
            if (StringUtils.isBlank(annotationText) || cell.getCellComment() != null) {
                continue;
            }
            if (drawingPatriarch == null) {
                drawingPatriarch = sheet.createDrawingPatriarch();
            }
            Comment comment = drawingPatriarch.createCellComment(new XSSFClientAnchor(
                    0, 0, 0, 0,
                    cellIndex, row.getRowNum(),
                    cellIndex + 2, row.getRowNum() + 3));
            comment.setString(new XSSFRichTextString(annotationText));
            comment.setAuthor("NS-LIMS");
            cell.setCellComment(comment);
        }
    }
    private String buildKey(int rowIndex, int cellIndex) {
        return rowIndex + "_" + cellIndex;
    }
}
inspect-server/src/main/java/com/ruoyi/inspect/excel/handler/StaffAttendanceHeaderWriteHandler.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,105 @@
package com.ruoyi.inspect.excel.handler;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.handler.context.SheetWriteHandlerContext;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Collections;
import java.util.List;
/**
 * äººå‘˜è€ƒå‹¤å¯¼å‡ºåŠ¨æ€è¡¨å¤´å¤„ç†å™¨
 */
public class StaffAttendanceHeaderWriteHandler implements SheetWriteHandler {
    private static final int TITLE_ROW_INDEX = 1;
    private static final int WEEK_ROW_INDEX = 2;
    private static final int DAY_ROW_INDEX = 3;
    private static final int START_COLUMN_INDEX = 2;
    private static final int MAX_DATE_COLUMN_COUNT = 31;
    private static final DateTimeFormatter TITLE_FORMATTER = DateTimeFormatter.ofPattern("yyyyå¹´M月");
    private final List<LocalDate> attendanceDateList;
    public StaffAttendanceHeaderWriteHandler(List<LocalDate> attendanceDateList) {
        this.attendanceDateList = attendanceDateList == null ? Collections.emptyList() : attendanceDateList;
    }
    @Override
    public void afterSheetCreate(SheetWriteHandlerContext context) {
        Sheet sheet = context.getWriteSheetHolder().getSheet();
        if (sheet == null || attendanceDateList.isEmpty()) {
            return;
        }
        Row titleRow = sheet.getRow(TITLE_ROW_INDEX);
        if (titleRow != null) {
            Cell titleCell = titleRow.getCell(0);
            if (titleCell != null) {
                titleCell.setCellValue(attendanceDateList.get(attendanceDateList.size() - 1).format(TITLE_FORMATTER));
            }
        }
        Row weekRow = sheet.getRow(WEEK_ROW_INDEX);
        Row dayRow = sheet.getRow(DAY_ROW_INDEX);
        if (weekRow == null || dayRow == null) {
            return;
        }
        for (int i = 0; i < MAX_DATE_COLUMN_COUNT; i++) {
            Cell weekCell = getOrCreateCell(weekRow, START_COLUMN_INDEX + i);
            Cell dayCell = getOrCreateCell(dayRow, START_COLUMN_INDEX + i);
            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) {
        Cell cell = row.getCell(cellIndex);
        if (cell != null) {
            return cell;
        }
        Cell templateCell = row.getCell(START_COLUMN_INDEX);
        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 "";
        }
    }
}
inspect-server/src/main/java/com/ruoyi/inspect/mapper/InsOrderTemplateMapper.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.inspect.pojo.InsOrderTemplate;
import org.apache.ibatis.annotations.Mapper;
/**
* @author Administrator
@@ -9,6 +10,7 @@
* @createDate 2024-03-18 14:14:54
* @Entity com.yuanchu.mom.pojo.InsOrderTemplate
*/
@Mapper
public interface InsOrderTemplateMapper extends BaseMapper<InsOrderTemplate> {
}
inspect-server/src/main/java/com/ruoyi/inspect/pojo/InsOrder.java
@@ -224,6 +224,9 @@
    @ApiModelProperty("零件描述")
    private String partDesc;
    @ApiModelProperty("零件号")
    private String partNo;
    @ApiModelProperty("供应商名称")
    private String supplierName;
inspect-server/src/main/java/com/ruoyi/inspect/service/StaffAttendanceTrackingRecordService.java
@@ -8,6 +8,7 @@
import com.ruoyi.inspect.vo.StaffAttendanceVO;
import com.ruoyi.performance.dto.PerformanceShiftMapDto;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.util.List;
@@ -28,4 +29,5 @@
    boolean saveOrUpdateRecord(StaffAttendanceDTO staffAttendanceDTO);
    void exportStaffAttendanceRecords(HttpServletResponse response, StaffAttendanceDTO staffAttendanceDTO);
}
inspect-server/src/main/java/com/ruoyi/inspect/service/impl/RawMaterialOrderServiceImpl.java
@@ -403,7 +403,7 @@
        insOrderMapper.updateById(insOrder);
        // æ·»åŠ å·¥æ—¶
        addAuxiliary(insOrder, ifsInventoryQuantity);
//        addAuxiliary(insOrder, ifsInventoryQuantity);
        // todo: ifs直接移库
        insReportService.isRawMaterial(insOrder,true,true);
@@ -498,7 +498,7 @@
        insOrderMapper.insert(insOrder);
        // æ·»åŠ å·¥æ—¶
        addAuxiliary(insOrder, ifsInventoryQuantity);
//        addAuxiliary(insOrder, ifsInventoryQuantity);
        // todo: ifs直接移库
        insReportService.isRawMaterial(insOrder,true,true);
@@ -636,7 +636,7 @@
        insOrderMapper.updateById(insOrder);
        // æ·»åŠ å·¥æ—¶
        addAuxiliary(insOrder, ifsInventoryQuantity);
//        addAuxiliary(insOrder, ifsInventoryQuantity);
        return insOrder.getId();
    }
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å¹´M月")));
            }
        }
        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);
    }
    /**
inspect-server/src/main/java/com/ruoyi/inspect/task/SyncStaffAttendanceRecordSchedule.java
@@ -22,8 +22,7 @@
    @Autowired
    private StaffAttendanceTrackingRecordService trackingRecordService;
    @Scheduled(cron = "0 0 */12 * * ?")
    @Scheduled(cron = "0 0 0,12 * * ?")
    public void sync() {
        log.info("--------同步考勤记录定时任务开始--------");
        LocalDate yesterday = LocalDate.now(ZoneId.of("Asia/Shanghai")).minusDays(1L);
inspect-server/src/main/java/com/ruoyi/inspect/vo/InsOrderPrintingVo.java
@@ -44,4 +44,7 @@
    @ApiModelProperty("订单状态")
    private Integer insState;
    @ApiModelProperty("零件颜色")
    private String partColor;
}
inspect-server/src/main/java/com/ruoyi/inspect/vo/StaffAttendanceVO.java
@@ -99,5 +99,9 @@
    private LocalDateTime updateTime;
    /**
     * ç­æ¬¡æ—¶é—´å·®ï¼Œåˆ¤æ–­ç­æ¬¡æ˜¯å¦è·¨å¤©
     */
    private Double diffHour;
}
inspect-server/src/main/resources/mapper/InsOrderMapper.xml
@@ -396,12 +396,14 @@
                     io2.entrust_code,
                     io2.test_quantity,
                     io2.ins_state,
                     sto.color AS part_color,
                     JSON_OBJECT(
                             'sample_name', io2.sample_view,
                             'entrust_code', io2.entrust_code
                     )                                                 labelBarCode
              from ins_order io2
                       LEFT JOIN ins_sample isa ON io2.id = isa.ins_order_id
                LEFT JOIN ins_sample isa ON io2.id = isa.ins_order_id
                LEFT JOIN structure_test_object_part sto ON sto.part_no = io2.part_no COLLATE utf8mb4_general_ci
              where type_source = -1
              GROUP BY io2.id) a
        where insOrderId in
inspect-server/src/main/resources/static/staff_attendance_template.xlsx
Binary files differ
performance-server/src/main/java/com/ruoyi/performance/dto/PerformanceShiftMapDto.java
@@ -45,4 +45,7 @@
    @ApiModelProperty("班次结束时间")
    private String endTime;
    @ApiModelProperty("班次名称")
    private String shiftName;
}
performance-server/src/main/resources/mapper/PerformanceShiftMapper.xml
@@ -154,10 +154,13 @@
            u.name AS user_name,
            u.account AS person_code,
            st.start_time,
            st.end_time
            st.end_time,
            sd.dict_label AS shift_name,
            ps.annotation_text
        FROM performance_shift ps
        LEFT JOIN user u ON ps.user_id = u.id
        inner JOIN user u ON ps.user_id = u.id
        LEFT JOIN shift_time st ON ps.shift = st.shift
        left join sys_dict_data sd on ps.shift = sd.dict_value AND sd.dict_type='sys_class_type'
        <where>
            <if test="startTime!=null and endTime!=null">
                AND ps.work_time BETWEEN #{startTime} AND #{endTime}
@@ -166,6 +169,6 @@
                AND (u.account like concat('%',#{keyword},'%') OR u.name like concat('%',#{keyword},'%'))
            </if>
        </where>
        ORDER BY ps.work_time
        ORDER BY ps.work_time,ps.user_id
    </select>
</mapper>