zouyu
2026-04-20 1702e168020f36627c7de0b145e7e4f67a0fb2e1
performance-server/src/main/java/com/ruoyi/performance/service/impl/PerformanceShiftServiceImpl.java
@@ -2,27 +2,48 @@
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import com.baomidou.mybatisplus.core.metadata.IPage;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
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.google.common.util.concurrent.AtomicDouble;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.core.domain.entity.User;
import com.ruoyi.common.utils.JackSonUtil;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.performance.dto.PerformanceShiftAddDto;
import com.ruoyi.performance.dto.PerformanceShiftMapDto;
import com.ruoyi.performance.dto.StaffAttendanceDTO;
import com.ruoyi.performance.excel.PerformanceShiftAnnotationTextExcelData;
import com.ruoyi.performance.excel.PerformanceShiftExcelData;
import com.ruoyi.performance.excel.handler.performance.CommentWriteHandler;
import com.ruoyi.performance.mapper.PerformanceShiftMapper;
import com.ruoyi.performance.pojo.PerformanceShift;
import com.ruoyi.performance.pojo.StaffAttendanceTrackingRecord;
import com.ruoyi.performance.service.PerformanceShiftService;
import com.ruoyi.performance.service.StaffAttendanceTrackingRecordService;
import com.ruoyi.performance.vo.StaffAttendanceVO;
import com.ruoyi.system.mapper.UserMapper;
import com.ruoyi.system.service.ISysDictTypeService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.ibatis.annotations.Param;
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 org.springframework.util.ObjectUtils;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
@@ -30,6 +51,9 @@
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
/**
 * <p>
@@ -39,6 +63,7 @@
 * @author 江苏鵷雏网络科技有限公司
 * @since 2024-05-08 09:12:04
 */
@Slf4j
@Service
public class PerformanceShiftServiceImpl extends ServiceImpl<PerformanceShiftMapper, PerformanceShift> implements PerformanceShiftService {
@@ -46,74 +71,63 @@
    private ISysDictTypeService dictTypeService;
    @Autowired
    private StaffAttendanceTrackingRecordService trackingRecordService;
    @Autowired
    UserMapper userMapper;
    private DateTimeFormatter yyyyMMdd = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private DateTimeFormatter yyyyMMddHHmmss = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private DateTimeFormatter yyyMMStr = DateTimeFormatter.ofPattern("yyyy年MM月dd日");
    private final int LIST_MAX_COUNT = 1000;
    private static final String morningShiftKeyword = "早";//早班班次关键字
    private static final String dayShiftKeyword = "中";//中班班次关键字
    private static final String nightShiftKeyword = "夜";//夜班班次关键字
    private static final String holidayLeaveKeyword = "休";//休假,调休假班次关键字
    private static final String officialTripKeyword = "公";//公差班次关键字
    private static final String personalLeaveKeyword = "事";//事假班次关键字
    private static final String sickLeaveKeyword = "病";//病假班次关键字
    private static final String annualLeaveKeyword = "年";//年假班次关键字
    private static final String marriageLeaveKeyword = "婚";//婚假班次关键字
    private static final String maternityLeaveKeyword = "产";//产假班次关键字
    private static final String bereavementLeaveKeyword = "丧";//丧假班次关键字
    private static final List<String> shiftSoreList = Arrays.asList("早","中","夜","休","公","事","病","年","婚","产","丧");
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void performanceShiftAdd(PerformanceShiftAddDto performanceShiftAddDto) {
        List<PerformanceShift> list = new ArrayList<>();
        LocalDateTime startWeek = performanceShiftAddDto.getStartWeek();
        LocalDateTime endWeek = performanceShiftAddDto.getEndWeek();
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String formattedDateTime = performanceShiftAddDto.getStartWeek().format(formatter);
        String[] splitUserId = performanceShiftAddDto.getUserId().split(",");
        for (String userId : splitUserId) {
            //判断是否跨月
            boolean isMonth = startWeek.getMonthValue() != endWeek.getMonthValue();
            if (isMonth){
                //如果跨月,则两个月都判断一下看数据库是哪个月份的数据没有
                boolean exists1 = baseMapper.exists(Wrappers.<PerformanceShift>lambdaQuery()
                        .eq(PerformanceShift::getWorkTime, startWeek)
                        .eq(PerformanceShift::getUserId, userId));
                boolean exists2 = baseMapper.exists(Wrappers.<PerformanceShift>lambdaQuery()
                        .eq(PerformanceShift::getWorkTime, endWeek)
                        .eq(PerformanceShift::getUserId, userId));
                if (!exists1 && !exists2){
                    //两个月都不存在数据
                    list = saveMonth(performanceShiftAddDto.getStartWeek(), userId, list);
                    list = saveMonth(performanceShiftAddDto.getEndWeek(), userId, list);
                }else if (!exists1 && exists2){
                    //开始的月份不存在数据
                    list = saveMonth(performanceShiftAddDto.getStartWeek(), userId, list);
                }else if (exists1 && !exists2){
                    //结束的月份不存在数据
                    list = saveMonth(performanceShiftAddDto.getEndWeek(), userId, list);
        //1.查询所选周次时间范围内已排班的数据
        List<PerformanceShift> shiftList = baseMapper.selectList(Wrappers.<PerformanceShift>lambdaQuery()
                .between(ObjectUtils.allNotNull(performanceShiftAddDto.getStartWeek(), performanceShiftAddDto.getEndWeek()),
                        PerformanceShift::getWorkTime, performanceShiftAddDto.getStartWeek(), performanceShiftAddDto.getEndWeek())
                .in(!performanceShiftAddDto.getUserIdList().isEmpty(), PerformanceShift::getUserId, performanceShiftAddDto.getUserIdList())
        );
        List<LocalDateTime> timeList = getLocalDateTimesBetween(performanceShiftAddDto.getStartWeek(), performanceShiftAddDto.getEndWeek());
        //处理选中人员
        List<PerformanceShift> newShiftList = new ArrayList<>();
        performanceShiftAddDto.getUserIdList().forEach(userId->{
            List<PerformanceShift> oldShifts = shiftList.stream().filter(f -> Objects.equals(f.getUserId(), userId)).collect(Collectors.toList());
            timeList.forEach(time->{
                PerformanceShift performanceShift = oldShifts.stream().filter(f -> f.getWorkTime().isEqual(time)).findFirst().orElse(new PerformanceShift(userId, time));
                if(Objects.isNull(performanceShift.getId())||StringUtils.isBlank(performanceShift.getShift())){
                    performanceShift.setShift(performanceShiftAddDto.getShift());
                    newShiftList.add(performanceShift);
                }
            }else {
                //不跨月
                boolean exists = baseMapper.exists(Wrappers.<PerformanceShift>lambdaQuery()
                        .in(PerformanceShift::getWorkTime, formattedDateTime)
                        .eq(PerformanceShift::getUserId, userId));
                // 如果不存在添加数据
                if (!exists) {
                    list = saveMonth(performanceShiftAddDto.getEndWeek(), userId, list);
                }
            });
            if(newShiftList.size()>LIST_MAX_COUNT){
                this.saveBatch(newShiftList);
                newShiftList.clear();
            }
        }
        if (!list.isEmpty()) {
            baseMapper.insertBatchSomeColumn(list);
            list.clear();
        }
        // 再次更新
        List<LocalDateTime> datesBetween = getLocalDateTimesBetween(performanceShiftAddDto.getStartWeek(), performanceShiftAddDto.getEndWeek());
        for (LocalDateTime date : datesBetween) {
            for (String s : splitUserId) {
                PerformanceShift performanceShift = new PerformanceShift();
                performanceShift.setShift(performanceShiftAddDto.getShift());
                performanceShift.setUserId(Integer.valueOf(s));
                performanceShift.setWorkTime(date);
                String formatterDateTime = date.format(formatter);
                baseMapper.update(new PerformanceShift(), Wrappers.<PerformanceShift>lambdaUpdate()
                        .set(PerformanceShift::getShift, performanceShiftAddDto.getShift())
                        .eq(PerformanceShift::getUserId, s)
                        .eq(PerformanceShift::getWorkTime, formatterDateTime));
            }
        }
        });
        if(!newShiftList.isEmpty())this.saveOrUpdateBatch(newShiftList);
    }
    private List<PerformanceShift>   saveMonth (LocalDateTime week,String userId,List<PerformanceShift> list){
    private List<PerformanceShift> saveMonth (LocalDateTime week,String userId,List<PerformanceShift> list){
        LocalDate firstDayOfMonth = week.toLocalDate().withDayOfMonth(1);
        LocalDate lastDayOfMonth = week.toLocalDate().with(TemporalAdjusters.lastDayOfMonth());
        List<LocalDateTime> localDateTimesBetween = getLocalDateTimesBetween(firstDayOfMonth.atStartOfDay(), lastDayOfMonth.atStartOfDay());
@@ -132,114 +146,105 @@
    }
    @Override
    public Map<String, Object> performanceShiftPage(Page<Object> page, String time, String userName, String laboratory) {
        //查询当前登录人员的架构
        Integer userId = SecurityUtils.getUserId().intValue();
        //判断全部,个人,组织的权限
        User user = userMapper.selectById(userId);//当前登录的人
        //获取当前人所属实验室id
        String departLimsId = user.getDepartLimsId();
        if (com.baomidou.mybatisplus.core.toolkit.ObjectUtils.isNotEmpty(departLimsId) && !departLimsId.equals("")) {
            String[] split = departLimsId.split(",");
            //查询对应架构名称(通信实验室,电力实验室,检测办)
            String departLims = baseMapper.seldepLimsId(Integer.parseInt(split[split.length - 1]));
            if (departLims.contains("实验室")) {
                laboratory = departLims;
            }
        }
        // 获取header时间
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        DateTimeFormatter formatters = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // 将字符串时间转换为 LocalDateTime 类型时间
        LocalDateTime localDateTime = LocalDateTime.parse(time, formatters);
    public Map<String, Object> performanceShift( String time, String userName, String laboratory) {
        //查询人员架构
        List<User> userList = userMapper.selectUserListByPerformance(false);
        List<Integer> userIdList = userList.stream().map(User::getId).collect(Collectors.toList());
        //班次时间范围为上个月的26号到本月的25号
        LocalDateTime localDateTime = LocalDateTime.parse(time, yyyyMMddHHmmss);
        LocalDate firstDayOfMonth = localDateTime.toLocalDate().minusMonths(1L).withDayOfMonth(26);
        LocalDate lastDayOfMonth = localDateTime.toLocalDate().withDayOfMonth(25);
        //人员排班详情
        List<PerformanceShiftMapDto> mapIPage = baseMapper.performanceShift(firstDayOfMonth,lastDayOfMonth, userName, laboratory);
        Map<Integer, List<PerformanceShiftMapDto>> groupByUserId = mapIPage.stream().collect(Collectors.groupingBy(PerformanceShiftMapDto::getUserId));
        List<PerformanceShiftMapDto> newRecords = new ArrayList<>();
        groupByUserId.keySet().forEach(key->{
            PerformanceShiftMapDto shiftMapDto = new PerformanceShiftMapDto();
            List<PerformanceShiftMapDto> shiftMapDtos = groupByUserId.get(key);
            //统计各班次天数
            Map<String, Long> countShift = countShift(shiftMapDtos);
            shiftMapDto.setMonthlyAttendance(countShift);
            shiftMapDto.setMonthlyAttendanceStr(formateMap(countShift));
            shiftMapDto.setList(shiftMapDtos);
        IPage<PerformanceShiftMapDto> mapIPage = baseMapper.performanceShiftPage(page, firstDayOfMonth,lastDayOfMonth, userName, laboratory);
        List<SysDictData> shiftType = dictTypeService.selectDictDataByName("班次类型");
        List<Map<String, Object>> mapYearIPage = baseMapper.performanceShiftYearPage(time, userName, laboratory);
        mapIPage.getRecords().forEach(i -> {
            String[] shiftTimes = i.getShiftTime().split(";");
            double totalAttendance = 0;
            List<Map<String, Object>> map = new ArrayList<>();
            // 分割日期
            for (String shiftTime : shiftTimes) {
                Map<String, Object> hashMap = new HashMap<>();
                String[] shiftTimeAndShift = shiftTime.split(":");
                for (SysDictData enums : shiftType) {
                    if (!i.getMonthlyAttendance().containsKey(enums.getDictLabel())) {
                        i.getMonthlyAttendance().put(enums.getDictLabel(), 0);
                    }
                    if (enums.getDictValue().equals(shiftTimeAndShift[1])) {
                        BigDecimal bigDecimal = new BigDecimal(i.getMonthlyAttendance().get(enums.getDictLabel()).toString());
                        i.getMonthlyAttendance().put(enums.getDictLabel(), bigDecimal.add(new BigDecimal("1")));
                    }
                    // 半,另外半天算给早
//                    if (shiftTimeAndShift[1].equals("5") && enums.getDictValue().equals("0")) {
//                        BigDecimal bigDecimal = new BigDecimal(i.getMonthlyAttendance().get(enums.getDictLabel()).toString());
//                        i.getMonthlyAttendance().put(enums.getDictLabel(), bigDecimal.add(new BigDecimal("0.5")));
//                    }
                }
                // 早,中,夜,差
                if (shiftTimeAndShift[1].equals("2") || shiftTimeAndShift[1].equals("3") || shiftTimeAndShift[1].equals("4")) {
                    i.getMonthlyAttendance().put("totalAttendance", totalAttendance += 1);
                }
                // 半
//                if (shiftTimeAndShift[1].equals("5")) {
//                    i.getMonthlyAttendance().put("totalAttendance", totalAttendance += 0.5);
//                }
                hashMap.put("id", shiftTimeAndShift[3]);
                hashMap.put("shift", shiftTimeAndShift[1]);
                hashMap.put("time", shiftTimeAndShift[0]);
                hashMap.put("annotationText", shiftTimeAndShift[2]);
                map.add(hashMap);
            }
            double totalYearAttendance = 0;
            Map<String, Object> hashMap = new HashMap<>();
            for (Map<String, Object> record : mapYearIPage) {
                if (record.get("user_id").toString().equals(i.getUserId())) {
                    for (SysDictData enums : shiftType) {
                        if (!hashMap.containsKey(enums.getDictLabel())) {
                            hashMap.put(enums.getDictLabel(), 0);
                        }
                        if (enums.getDictValue().equals(record.get("shift"))) {
                            BigDecimal num = new BigDecimal(hashMap.get(enums.getDictLabel()).toString());
                            hashMap.put(enums.getDictLabel(), num.add(new BigDecimal("1")));
                        }
                        // 半,另外半天算给早
//                        if (record.get("shift").equals("5") && enums.getDictValue().equals("0")) {
//                            BigDecimal bigDecimal = new BigDecimal(hashMap.get(enums.getDictLabel()).toString());
//                            hashMap.put(enums.getDictLabel(), bigDecimal.add(new BigDecimal("0.5")));
//                        }
                    }
                    if (record.get("shift").equals("2") || record.get("shift").equals("3") || record.get("shift").equals("4")) {
                        hashMap.put("totalAttendance", totalYearAttendance += 1);
                    }
                    // 半
//                    if (record.get("shift").equals("5")) {
//                        hashMap.put("totalAttendance", totalYearAttendance += 0.5);
//                    }
                }
            }
            i.setSidebarAnnualAttendance(hashMap);
            i.setList(map);
            i.setShiftTime(null);
            shiftMapDto.setUserName(shiftMapDtos.isEmpty()?"":shiftMapDtos.get(0).getUserName());
            shiftMapDto.setUserId(key);
            newRecords.add(shiftMapDto);
        });
        newRecords.sort(Comparator.comparing(r->userIdList.indexOf(r.getUserId())));
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("page", newRecords);
        resultMap.put("headerList", getYearHeaderTimeList(firstDayOfMonth,lastDayOfMonth));
        return resultMap;
    }
    public String formateMap(Map<String, Long> map){
        List<String> stringList = new ArrayList<>();
        map.forEach((k,v)->{
            if(shiftSoreList.contains(k)){
                stringList.add(k+":"+v);
            }
        });
        return String.join(",",stringList);
    }
    /**
     * 统计班次
     * @param shiftMapDtos 班次列表
     */
    private Map<String,Long> countShift(List<PerformanceShiftMapDto> shiftMapDtos){
        TreeMap<String, Long> targetMap = new TreeMap<>(Comparator.comparing(shiftSoreList::indexOf));
        //汇总早班、中班、夜班、休息、请假、出差的天数,以及总出勤天数
        Map<String, Long> groupByShiftName = shiftMapDtos.stream().filter(f-> StringUtils.isNotBlank(f.getShiftName())).collect(Collectors.groupingBy(PerformanceShiftMapDto::getShiftName,Collectors.counting()));
        AtomicLong morningShiftCount = new AtomicLong(0);//早班
        AtomicLong dayShiftCount = new AtomicLong(0);//中班
        AtomicLong nightShiftCount = new AtomicLong(0);//晚班
        AtomicLong holidayShiftCount = new AtomicLong(0);//休
        AtomicLong totalCount = new AtomicLong(0);//总出勤天数
        groupByShiftName.keySet().forEach(key->{
            if(key.contains(morningShiftKeyword)){
                morningShiftCount.getAndAdd(groupByShiftName.get(key));
                totalCount.getAndAdd(groupByShiftName.get(key));
            }else if(key.contains(dayShiftKeyword)){
                dayShiftCount.getAndAdd(groupByShiftName.get(key));
                totalCount.getAndAdd(groupByShiftName.get(key));
            }else if(key.contains(nightShiftKeyword)){
                nightShiftCount.getAndAdd(groupByShiftName.get(key));
                totalCount.getAndAdd(groupByShiftName.get(key));
            }else if(key.contains(holidayLeaveKeyword)){
                holidayShiftCount.getAndAdd(groupByShiftName.get(key));
            }else if(key.contains(officialTripKeyword)){
                targetMap.put(key,groupByShiftName.get(key));
                totalCount.getAndAdd(groupByShiftName.get(key));
            }else{
                targetMap.put(key,groupByShiftName.get(key));
            }
        });
        targetMap.put("早",morningShiftCount.get());
        targetMap.put("中",dayShiftCount.get());
        targetMap.put("夜",nightShiftCount.get());
        targetMap.put("休",holidayShiftCount.get());
        targetMap.put("totalCount",totalCount.get());
        return targetMap;
    }
    /**
     * 班次分页查询:获取月度日期表头列表
     * @param firstDayOfMonth
     * @param lastDayOfMonth
     * @return
     */
    private List<Object> getYearHeaderTimeList(LocalDate firstDayOfMonth,LocalDate lastDayOfMonth){
        List<LocalDateTime> localDateTimesBetween = getLocalDateTimesBetween(firstDayOfMonth.atStartOfDay(), lastDayOfMonth.atStartOfDay());
        List<Object> list1 = new ArrayList<>();
        List<Object> list = new ArrayList<>();
        for (LocalDateTime dateTime : localDateTimesBetween) {
            Map<Object, Object> hashMap = new HashMap<>();
            DateTime parse = DateUtil.parse(dateTime.format(formatter));
            DateTime parse = DateUtil.parse(dateTime.format(yyyyMMdd));
            hashMap.put("weekly", DateUtil.weekOfYear(DateUtil.offsetDay(parse, 1)));
            hashMap.put("headerTime", getWeek(dateTime.format(formatters)));
            list1.add(hashMap);
            hashMap.put("headerTime", getWeek(dateTime.format(yyyyMMddHHmmss)));
            list.add(hashMap);
        }
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("page", mapIPage);
        resultMap.put("headerList", list1);
        return resultMap;
        return list;
    }
    @Override
@@ -250,190 +255,304 @@
    }
    @Override
    public IPage<Map<String, Object>> performanceShiftPageYear(Page<Object> page, String time, String userName, String laboratory) {
        //查询当前登录人员的架构
        Integer userId = SecurityUtils.getUserId().intValue();
        //判断全部,个人,组织的权限
        User user = userMapper.selectById(userId);//当前登录的人
        //获取当前人所属实验室id
        String departLimsId = user.getDepartLimsId();
        if (com.baomidou.mybatisplus.core.toolkit.ObjectUtils.isNotEmpty(departLimsId) && !departLimsId.equals("")) {
            String[] split = departLimsId.split(",");
            //查询对应架构名称(通信实验室,电力实验室,检测办)
            String departLims = baseMapper.seldepLimsId(Integer.parseInt(split[split.length - 1]));
            if (departLims.contains("实验室")) {
                laboratory = departLims;
            }
    public List<Map<String, Object>> performanceShiftPageYear(LocalDateTime time, String userName, String laboratory) {
        //查询人员架构
        List<User> userList = userMapper.selectUserListByPerformance(false);
        List<Integer> userIdList = userList.stream().map(User::getId).collect(Collectors.toList());
        if(ObjectUtil.isEmpty(time)){
            throw new RuntimeException("查询日期不能为空");
        }
        IPage<Map<String, Object>> mapYearIPage = baseMapper.performanceShiftYear(page, time, userName, laboratory);
        List<SysDictData> shiftType = dictTypeService.selectDictDataByName("班次类型");
        mapYearIPage.setRecords(annualAttendanceProcessing(mapYearIPage.getRecords(), shiftType));
        return mapYearIPage;
    }
    // 年分页与导出共同使用
    public List<Map<String, Object>> annualAttendanceProcessing(List<Map<String, Object>> mapYearList, List<SysDictData> shiftType) {
        for (Map<String, Object> map : mapYearList) {
            Map<String, Object> resultMap = new LinkedHashMap<>();
            Map<String, Object> hashMapYear = new LinkedHashMap<>();
            double totalYearAttendance = 0;
            // 一年12个月
            for (int i = 1; i < 13; i++) {
                Map<String, Object> hashMapMonth = new LinkedHashMap<>();
                double totalMonthAttendance = 0;
                for (SysDictData shift : shiftType) {
                    // 初始化赋值
                    if (!hashMapYear.containsKey(shift.getDictLabel())) {
                        hashMapYear.put(shift.getDictLabel(), 0);
        LocalDateTime startDateTime = time.minusMonths(1L).withDayOfMonth(26);
        LocalDateTime endDateTime = time.plusMonths(11L).withDayOfMonth(25);
        List<PerformanceShiftMapDto> shiftYearList = baseMapper.performanceShiftYear(startDateTime,endDateTime, userName, laboratory);
        //按人员分组,统计每个人的年度班次
        Map<Integer, List<PerformanceShiftMapDto>> groupByUserId = shiftYearList.stream().collect(Collectors.groupingBy(PerformanceShift::getUserId));
        List<Map<String,Object>> returnList = new ArrayList<>();
        groupByUserId.keySet().forEach(userIdKey->{
            //按月份分组,统计每个月的班次详情
            Map<String, Object> returnMap = new HashMap<>();
            List<PerformanceShiftMapDto> shiftMapDtos = groupByUserId.get(userIdKey);
            Map<String, Long> sidebarAnnualAttendance = countShift(shiftMapDtos);//年度班次统计
            List<Map<String, Object>> monthlyAttendances = new ArrayList<>();
            if(!shiftMapDtos.isEmpty()){
                returnMap.put("userName",shiftMapDtos.get(0).getUserName());
                returnMap.put("userId",shiftMapDtos.get(0).getUserId());
                //遍历12个月的班次信息
                for (int i = 0; i < 11; i++) {
                    LocalDateTime firstDayOfMonth = startDateTime.plusMonths(i);
                    LocalDateTime lastDayOfMonth = firstDayOfMonth.plusMonths(1L).withDayOfMonth(25);
                    Map<String, Object> monthlyAttendanceMap = new HashMap<>();
                    List<PerformanceShiftMapDto> monthShiftDtos = shiftMapDtos.stream().filter(f->!f.getWorkTime().isBefore(firstDayOfMonth)&&!f.getWorkTime().isAfter(lastDayOfMonth)).collect(Collectors.toList());
                    if(CollectionUtils.isEmpty(monthShiftDtos)){
                        monthlyAttendanceMap.put("monthlyAttendanceStr",formateMap(countShift(new ArrayList<>())));//月度班次统计
                        monthlyAttendanceMap.put("monthlyAttendance",countShift(new ArrayList<>()));//月度班次统计
                    }else{
                        monthlyAttendanceMap.put("monthlyAttendanceStr",formateMap(countShift(monthShiftDtos)));//月度班次统计
                        monthlyAttendanceMap.put("monthlyAttendance",countShift(monthShiftDtos));//月度班次统计
                    }
                    // 月
                    if (!ObjectUtils.isEmpty(map.get("month_str"))) {
                        String charArray = map.get("month_str").toString();
                        int count = countOccurrences(charArray, i + ":" + shift.getDictValue());
                        hashMapMonth.put(shift.getDictLabel(), count);
                        hashMapYear.put(shift.getDictLabel(), new BigDecimal(hashMapYear.get(shift.getDictLabel()).toString()).add(new BigDecimal(count)));
                        // 早,中,夜,差
                        if (shift.getDictValue().equals("0") || shift.getDictValue().equals("1") || shift.getDictValue().equals("2") || shift.getDictValue().equals("6")) {
                            totalMonthAttendance += count;
                            totalYearAttendance += count;
                        }
//                      半,另外半天算给早
                        if (shift.getDictValue().equals("5")) {
                            BigDecimal multiply = new BigDecimal("0.5").multiply(new BigDecimal(count)).setScale(1, BigDecimal.ROUND_CEILING);
                            hashMapMonth.put(shiftType.get(0).getDictLabel(), new BigDecimal(hashMapMonth.get(shiftType.get(0).getDictLabel()).toString()).add(multiply));
                            hashMapYear.put(shiftType.get(0).getDictLabel(), new BigDecimal(hashMapYear.get(shiftType.get(0).getDictLabel()).toString()).add(multiply));
                            totalMonthAttendance += multiply.doubleValue();
                            totalYearAttendance += multiply.doubleValue();
                        }
                    }
                    // 空数据
                    else {
                        map.put("work_time", i);
                        hashMapMonth.put(shift.getDictLabel(), 0);
                    }
                    monthlyAttendances.add(monthlyAttendanceMap);
                }
                hashMapMonth.put("totalMonthAttendance", totalMonthAttendance);
                hashMapYear.put("totalYearAttendance", totalYearAttendance);
                resultMap.put(i + "", hashMapMonth);
                returnMap.put("monthlyAttendances",monthlyAttendances);
                returnMap.put("sidebarAnnualAttendance",sidebarAnnualAttendance);
                returnMap.put("sidebarAnnualAttendanceStr",formateMap(sidebarAnnualAttendance));
            }
            map.remove("month_str");
            map.remove("year_str");
            map.put("year", hashMapYear);
            map.put("month", resultMap);
        }
        return mapYearList;
    }
    public static int countOccurrences(String str, String target) {
        int count = 0;
        int index = 0;
        while ((index = str.indexOf(target, index)) != -1) {
            count++;
            index += target.length();
        }
        return count;
    }
    public List<List<Object>> dataRequiredForProcessingIntoExcel(List<Map<String, Object>> list, List<SysDictData> enums) throws Exception {
        List<List<Object>> data = new ArrayList<>();
        for (int i = 0; i < list.size(); i++) {
            List<Object> excelRowList = new ArrayList<>();
            excelRowList.add(i + 1);
            excelRowList.add(list.get(i).get("account"));
            excelRowList.add(list.get(i).get("name"));
            Map<String, Object> year = JackSonUtil.unmarshal(JackSonUtil.marshal(list.get(i).get("year")), Map.class);
            excelRowList.add(year.get("totalYearAttendance"));
            enums.forEach(j -> {
                if (!j.getDictValue().equals("5")) {
                    excelRowList.add(year.get(j.getDictLabel()));
                }
            });
            Map<String, Map<String, Object>> month = JackSonUtil.unmarshal(JackSonUtil.marshal(list.get(i).get("month")), Map.class);
            for (int j = 1; j < 13; j++) {
                Object totalMonthAttendance = month.get(j + "").get("totalMonthAttendance");
                excelRowList.add(totalMonthAttendance);
                for (SysDictData anEnum : enums) {
                    if (!anEnum.getDictValue().equals("5")) {
                        excelRowList.add(month.get(j + "").get(anEnum.getDictLabel()));
                    }
                }
            }
            data.add(excelRowList);
        }
        return data;
    }
    @Override
    public Map<Object, Object> exportToYearExcel(String time, String userName, String laboratory) throws Exception {
        Map<Object, Object> map = new HashMap<>();
        List<SysDictData> shiftType = dictTypeService.selectDictDataByName("班次类型");
        DateTimeFormatter formatters = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // 将字符串时间转换为 LocalDateTime 类型时间
        LocalDateTime localDateTime = LocalDateTime.parse(time, formatters);
        map.put("header", getYearHeader(localDateTime.getYear() + " 年", shiftType));
        List<Map<String, Object>> mapYearList = baseMapper.performanceShiftYearList(time, userName, laboratory);
        annualAttendanceProcessing(mapYearList, shiftType);
        List<List<Object>> lists = dataRequiredForProcessingIntoExcel(mapYearList, shiftType);
        map.put("data", lists);
        return map;
    }
    @Override
    public Map<Object, Object> exportToMonthExcel(String time, String userName, String laboratory) {
        List<SysDictData> shiftType = dictTypeService.selectDictDataByName("班次类型");
        List<PerformanceShiftMapDto> mapIPage = baseMapper.performanceShiftList(time, userName, laboratory);
        mapIPage.forEach(i -> {
            String[] shiftTimes = i.getShiftTime().split(";");
            double totalAttendance = 0;
            List<Map<String, Object>> map = new ArrayList<>();
            // 分割日期
            for (String shiftTime : shiftTimes) {
                Map<String, Object> hashMap = new HashMap<>();
                String[] shiftTimeAndShift = shiftTime.split(":");
                for (SysDictData enums : shiftType) {
                    if (!i.getMonthlyAttendance().containsKey(enums.getDictLabel())) {
                        i.getMonthlyAttendance().put(enums.getDictLabel(), 0);
                    }
                    if (enums.getDictValue().equals(shiftTimeAndShift[1])) {
                        BigDecimal bigDecimal = new BigDecimal(i.getMonthlyAttendance().get(enums.getDictLabel()).toString());
                        i.getMonthlyAttendance().put(enums.getDictLabel(), bigDecimal.add(new BigDecimal("1")));
                    }
                    // 半,另外半天算给早
                    if (shiftTimeAndShift[1].equals("5") && enums.getDictValue().equals("0")) {
                        BigDecimal bigDecimal = new BigDecimal(i.getMonthlyAttendance().get(enums.getDictLabel()).toString());
                        i.getMonthlyAttendance().put(enums.getDictLabel(), bigDecimal.add(new BigDecimal("0.5")));
                    }
                }
                // 早,中,夜,差
                if (shiftTimeAndShift[1].equals("1") || shiftTimeAndShift[1].equals("2") || shiftTimeAndShift[1].equals("0") || shiftTimeAndShift[1].equals("6")) {
                    i.getMonthlyAttendance().put("totalAttendance", totalAttendance += 1);
                }
                // 半
                if (shiftTimeAndShift[1].equals("5")) {
                    i.getMonthlyAttendance().put("totalAttendance", totalAttendance += 0.5);
                }
                hashMap.put("id", shiftTimeAndShift[3]);
                hashMap.put("shift", shiftTimeAndShift[1]);
                hashMap.put("time", shiftTimeAndShift[0]);
                hashMap.put("annotationText", shiftTimeAndShift[2]);
                map.add(hashMap);
            }
            i.setList(map);
            i.setShiftTime(null);
            returnList.add(returnMap);
        });
        Map<Object, Object> map = new HashMap<>();
        DateTimeFormatter formatters = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // 将字符串时间转换为 LocalDateTime 类型时间
        LocalDateTime localDateTime = LocalDateTime.parse(time, formatters);
        map.put("header", getMonthHeader(localDateTime, shiftType));
        List<List<Object>> lists = dataRequiredForProcessingIntoExcelMonth(mapIPage, shiftType);
        map.put("data", lists);
        return map;
        returnList.sort(Comparator.comparing(r->userIdList.indexOf(Integer.parseInt(r.get("userId").toString()))));
        return returnList;
    }
    @Override
    public boolean editAnnotationText(PerformanceShift performanceShift) {
        return this.updateById(performanceShift);
    }
    @Override
    public void exportToExcel(LocalDateTime time, String userName, String laboratory, Boolean isMonth, HttpServletResponse response) {
        response.reset();
        try{
            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");
            Map<Object, Object> data;
            LocalDateTime startDate;
            LocalDateTime endDate;
            if(!isMonth){
                //年度统计,时间区间取一整年
                startDate = time.minusMonths(1L).withDayOfMonth(26);
                endDate = time.plusMonths(11L).withDayOfMonth(25);
            }else{
                //月度统计,时间区间取上个月26号到本月25号
                startDate = time.minusMonths(1L).withDayOfMonth(26);
                endDate = time.withDayOfMonth(25);
            }
            List<LocalDate> performanceShiftDateList = buildPerformanceShiftDateList(startDate,endDate,isMonth);
            //批注信息坐标信息
            List<PerformanceShiftAnnotationTextExcelData> annotationTextList = new ArrayList<>();
            // 查询班次
            List<PerformanceShiftMapDto> performanceShifts = baseMapper.selectListByWorkTime(startDate, endDate, userName);
            //获取考勤数据
            StaffAttendanceDTO staffAttendanceDTO = new StaffAttendanceDTO();
            staffAttendanceDTO.setStartDate(startDate);
            staffAttendanceDTO.setEndDate(endDate);
            staffAttendanceDTO.setKeyword(userName);
            List<StaffAttendanceVO> attendanceRecords = trackingRecordService.getAttendanceRecord(performanceShifts,staffAttendanceDTO);
            //组装导出数据
            List<PerformanceShiftExcelData> 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));
                PerformanceShiftExcelData performanceShiftExcelData = new PerformanceShiftExcelData();
                List<String> shiftList = new ArrayList<>();
                performanceShiftExcelData.setPersonName(shiftMapDtos.get(0).getUserName());
                performanceShiftExcelData.setExcelIndex(i+1);
                AtomicInteger morningShiftCount = new AtomicInteger(0);//早班天数
                AtomicInteger dayShiftCount = new AtomicInteger(0);//中班天数
                AtomicInteger nightShiftCount = new AtomicInteger(0);//夜班天数
                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 maternityLeaveCount = new AtomicInteger(0);//产假天数
                AtomicInteger totalCount = 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(),morningShiftKeyword)){
                        morningShiftCount.getAndIncrement();
                        totalCount.getAndIncrement();
                    }else if(StringUtils.contains(shiftMapDto.getShiftName(),dayShiftKeyword)){
                        dayShiftCount.getAndIncrement();
                        totalCount.getAndIncrement();
                    }else if(StringUtils.contains(shiftMapDto.getShiftName(),nightShiftKeyword)){
                        nightShiftCount.getAndIncrement();
                        totalCount.getAndIncrement();
                    }else 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();
                        totalCount.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();
                    }else if(StringUtils.contains(shiftMapDto.getShiftName(),maternityLeaveKeyword)){
                        maternityLeaveCount.getAndIncrement();
                    }
                    shiftList.add(shiftMapDto.getShiftName());
                    if(!StringUtils.isAllBlank(shiftMapDto.getStartTime(),shiftMapDto.getEndTime())){
                        //过滤当前人员的班次信息
                        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.isNotEmpty(vo)){
                            String actualWorkHours = Objects.toString(vo.getActualWorkHours(), "");
                            if (StringUtils.isNotBlank(actualWorkHours)) {
                                attendanceWorkHourCount.getAndAdd(Double.parseDouble(actualWorkHours));
                            }
                        }
                    }
                    //月度统计才插入批注数据
                    if(isMonth){
                        if(StringUtils.isNotBlank(shiftMapDto.getAnnotationText())){
                            annotationTextList.add(new PerformanceShiftAnnotationTextExcelData(i,j,shiftMapDto.getAnnotationText()));
                        }
                    }
                }
                if(isMonth){
                    performanceShiftExcelData.setShiftNameList(shiftList);
                }
                performanceShiftExcelData.setTotalCount(totalCount.get());
                //班次考勤天数
                performanceShiftExcelData.setMorningShiftCount(morningShiftCount.get());
                performanceShiftExcelData.setDayShiftCount(dayShiftCount.get());
                performanceShiftExcelData.setNightShiftCount(nightShiftCount.get());
                performanceShiftExcelData.setHolidayLeaveCount(holidayCount.get());
                performanceShiftExcelData.setPersonalLeaveCount(personalLeaveCount.get());
                performanceShiftExcelData.setAnnualLeaveCount(annualLeaveCount.get());
                performanceShiftExcelData.setOfficialTripCount(officialTripCount.get());
                performanceShiftExcelData.setMarriageLeaveCount(marriageLeaveCount.get());
                performanceShiftExcelData.setBereavementLeaveCount(bereavementLeaveCount.get());
                performanceShiftExcelData.setSickLeaveCount(sickLeaveCount.get());
                performanceShiftExcelData.setMaternityLeaveCount(maternityLeaveCount.get());
                performanceShiftExcelData.setTotalWorkHourCount(attendanceWorkHourCount.get());
                excelData.add(performanceShiftExcelData);
            }
            //导出
            InputStream resourceAsStream = buildPerformanceShiftTemplate(performanceShiftDateList,isMonth);
            try (ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).withTemplate(resourceAsStream).registerWriteHandler(new CommentWriteHandler(excelData, annotationTextList)).relativeHeadRowIndex(4).build()) {
                WriteSheet writeSheet = EasyExcel.writerSheet().build();
                excelWriter.fill(excelData, writeSheet);
                if(!isMonth){
                    String startDateStr = startDate.format(yyyMMStr);
                    String endDateStr = endDate.format(yyyMMStr);
                    Map<String, String> dateMap = new HashMap<>();
                    dateMap.put("startDate",startDateStr);
                    dateMap.put("endDate",endDateStr);
                    excelWriter.fill(dateMap, writeSheet);
                }
            }
        }catch (Exception e){
            throw new RuntimeException("班次导出异常");
        }
    }
    private InputStream buildPerformanceShiftTemplate(List<LocalDate> attendanceDateList,Boolean isMonth) throws IOException {
        String templateName = "/static/performance_shift_month_template.xlsx";
        if(!isMonth){
            templateName = "/static/performance_shift_year_template.xlsx";
        }
        try (InputStream templateStream = this.getClass().getResourceAsStream(templateName)) {
            assert templateStream != null;
            try (Workbook workbook = WorkbookFactory.create(templateStream);
                 ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                Sheet sheet = workbook.getSheetAt(0);
                if(isMonth){
                    fillPerformanceShiftHeader(sheet, attendanceDateList);
                }
                workbook.write(outputStream);
                return new ByteArrayInputStream(outputStream.toByteArray());
            }
        } catch (Exception e) {
            throw new IOException("构建班次导出模板失败", e);
        }
    }
    private void fillPerformanceShiftHeader(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 "";
        }
    }
    private List<LocalDate> buildPerformanceShiftDateList(LocalDateTime startDateTime,LocalDateTime endDateTime,Boolean isMonth) {
        if (startDateTime == null || endDateTime == null) {
            throw new IllegalArgumentException("导出时间范围不能为空");
        }
        LocalDate startDate = startDateTime.toLocalDate();
        LocalDate endDate = endDateTime.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 && isMonth) {
            throw new IllegalArgumentException("导出时间范围不能超过31天");
        }
        return attendanceDateList;
    }
    // 获取两个localDateTime的每一天
@@ -547,7 +666,7 @@
        for (int i = 0; i < list.size(); i++) {
            List<Object> excelRowList = new ArrayList<>();
            excelRowList.add(i + 1);
            excelRowList.add(list.get(i).getName());
            excelRowList.add(list.get(i).getUserName());
            excelRowList.add(list.get(i).getDepartment());
            excelRowList.add(list.get(i).getMonthlyAttendance().get("totalAttendance"));
            excelRowList.add(list.get(i).getMonthlyAttendance().get(enums.get(3).getDictLabel())); // 休
@@ -556,15 +675,7 @@
            excelRowList.add(list.get(i).getMonthlyAttendance().get(enums.get(1).getDictLabel())); // 中
            excelRowList.add(list.get(i).getMonthlyAttendance().get(enums.get(2).getDictLabel())); // 夜
            excelRowList.add(list.get(i).getMonthlyAttendance().get(enums.get(6).getDictLabel())); // 差
            for (Map<String, Object> o : list.get(i).getList()) {
                String enumLabel = "";
                for (SysDictData anEnum : enums) {
                    if (anEnum.getDictValue().equals(o.get("shift"))) {
                        enumLabel = anEnum.getDictLabel();
                    }
                }
                excelRowList.add(ObjectUtils.isEmpty(enumLabel) ? "-" : enumLabel);
            }
            data.add(excelRowList);
        }
        return data;