buhuazhen
8 天以前 b7eb6e87b4d88e5a419b64e3e3d0e5dc1be0f48f
Merge remote-tracking branch 'origin/dev_New' into dev_New
已添加11个文件
已修改6个文件
1309 ■■■■■ 文件已修改
pom.xml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/ScheduleTask.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalShiftController.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/PerformanceShiftAddDto.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/PerformanceShiftMapDto.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/PersonalShiftMapper.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceLocationConfig.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalShift.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/PersonalShiftService.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalShiftServiceImpl.java 488 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/task/PersonalAttendanceRecordsTask.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/utils/JackSonUtil.java 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/utils/StyleMonthUtils.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/utils/StyleYearUtils.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/PersonalShiftMapper.xml 106 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -39,6 +39,7 @@
        <tomcat.version>9.0.102</tomcat.version>
        <minio.version>8.4.3</minio.version>
        <okhttp.version>4.9.0</okhttp.version>
        <hutool.version>5.8.18</hutool.version>
        <logback.version>1.2.13</logback.version>
        <spring-security.version>5.7.12</spring-security.version>
        <spring-framework.version>5.3.39</spring-framework.version>
@@ -315,6 +316,13 @@
            <scope>compile</scope>
        </dependency>
        <!--hutool工具包-->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
src/main/java/com/ruoyi/ScheduleTask.java
@@ -55,4 +55,6 @@
                .eq(SysNotice::getStatus,"1")
                .lt(SysNotice::getCreateTime, LocalDateTime.now()));
    }
}
src/main/java/com/ruoyi/staff/controller/PersonalShiftController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,98 @@
package com.ruoyi.staff.controller;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.staff.dto.PerformanceShiftAddDto;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.pojo.PersonalShift;
import com.ruoyi.staff.service.PersonalAttendanceLocationConfigService;
import com.ruoyi.staff.service.PersonalShiftService;
import com.ruoyi.staff.utils.StyleMonthUtils;
import com.ruoyi.staff.utils.StyleYearUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
 * <p>
 *  å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-05 03:52:19
 */
@RestController
@RequestMapping("/personalShift")
@Api(tags = "人员排班")
public class PersonalShiftController {
    @Autowired
    private PersonalShiftService personalShiftService;
    @ApiOperation("人员排班")
    @PostMapping("/add")
    public R add(@RequestBody PerformanceShiftAddDto performanceShiftAddDto){
        return R.ok(personalShiftService.performanceShiftAdd(performanceShiftAddDto));
    }
    @ApiOperation(value = "月份分页查询")
    @GetMapping("page")
    public R performanceShiftPage(Integer size, Integer current, String time, String userName, Integer sysDeptId) {
        return R.ok(personalShiftService.performanceShiftPage(new Page<>(current, size), time, userName, sysDeptId));
    }
    @ApiOperation(value = "年份分页查询")
    @GetMapping("pageYear")
    public R performanceShiftPageYear(Integer size, Integer current, String time, String userName, Integer sysDeptId) {
        return R.ok(personalShiftService.performanceShiftPageYear(new Page<>(current, size), time, userName, sysDeptId));
    }
    @ApiOperation(value = "班次状态修改")
    @PostMapping("update")
    public R performanceShiftUpdate(@RequestBody PersonalShift personalShift) {
        personalShiftService.performanceShiftUpdate(personalShift);
        return R.ok();
    }
    @ApiOperation(value = "导出")
    @GetMapping("export")
    public void exportToExcel(@NotNull(message = "时间不能为空!") String time, String userName, Integer sysDeptId, Boolean isMonth, HttpServletResponse response) throws Exception {
        Map<Object, Object> data;
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setHeader("requestType","excel");
        response.setHeader("Access-Control-Expose-Headers", "requestType");
        if (!isMonth) {
            data = personalShiftService.exportToYearExcel(time, userName, sysDeptId);
            // è®¾ç½®å•元格样式
            HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(StyleYearUtils.getHeadStyle(), StyleYearUtils.getContentStyle());
            // ä¿å­˜åˆ°ç¬¬ä¸€ä¸ªsheet中
            EasyExcel.write(response.getOutputStream())
                    .head((List<List<String>>) data.get("header"))
                    .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // è‡ªé€‚应列宽
                    .registerWriteHandler(horizontalCellStyleStrategy)
                    .sheet("年度")
                    .doWrite((Collection<?>) data.get("data"));
        } else {
            data = personalShiftService.exportToMonthExcel(time, userName, sysDeptId);
            // è®¾ç½®å•元格样式
            HorizontalCellStyleStrategy horizontalCellStyleStrategy = new HorizontalCellStyleStrategy(StyleMonthUtils.getHeadStyle(), StyleMonthUtils.getContentStyle());
            EasyExcel.write(response.getOutputStream())
                    .head((List<List<String>>) data.get("header"))
                    .registerWriteHandler(horizontalCellStyleStrategy)
                    .sheet("月度")
                    .doWrite((Collection<?>) data.get("data"));
        }
    }
}
src/main/java/com/ruoyi/staff/dto/PerformanceShiftAddDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,33 @@
package com.ruoyi.staff.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
@Data
public class PerformanceShiftAddDto {
    @NotNull(message = "请选择班次")
    @ApiModelProperty("班次")
    private Integer personalAttendanceLocationConfigId;
    @NotNull(message = "请选择员工")
    @ApiModelProperty("员工id")
    private String staffOnJobId;
    @NotNull(message = "请选择周次")
    @ApiModelProperty("开始周次")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime startWeek;
    @NotNull(message = "请选择周次")
    @ApiModelProperty("结束周次")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime endWeek;
}
src/main/java/com/ruoyi/staff/dto/PerformanceShiftMapDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.ruoyi.staff.dto;
import lombok.Data;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Data
public class PerformanceShiftMapDto {
    private String name;
    private String shiftTime;
    private String userId;
    private String department;
    private Map<String, Object> monthlyAttendance = new HashMap<>();
    private List<Map<String, Object>> list = new ArrayList<>();
    private List<Map<Object, Object>> headerList = new ArrayList<>();
}
src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java
@@ -40,4 +40,7 @@
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime  endAt;
    //班次
    private String shift;
}
src/main/java/com/ruoyi/staff/mapper/PersonalShiftMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,34 @@
package com.ruoyi.staff.mapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.config.MyBaseMapper;
import com.ruoyi.staff.dto.PerformanceShiftMapDto;
import com.ruoyi.staff.pojo.PersonalShift;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
 * <p>
 *  Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-05 03:52:19
 */
@Mapper
public interface PersonalShiftMapper extends BaseMapper<PersonalShift> {
    IPage<PerformanceShiftMapDto> performanceShiftPage(Page<Object> page, @Param("time") String time, @Param("userName") String userName, @Param("sysDeptId") Integer sysDeptId);
    IPage<Map<String, Object>> performanceShiftYear(Page<Object> page, @Param("time") String time, @Param("userName") String userName, @Param("sysDeptId") Integer sysDeptId);
    List<Map<String, Object>> performanceShiftYearList(@Param("time") String time, @Param("userName") String userName, @Param("sysDeptId") Integer sysDeptId);
    List<PerformanceShiftMapDto> performanceShiftList(@Param("time") String time, @Param("userName") String userName, @Param("sysDeptId") Integer sysDeptId);
}
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceLocationConfig.java
@@ -57,4 +57,7 @@
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime  endAt;
    @ApiModelProperty("班次")
    private String shift;
}
src/main/java/com/ruoyi/staff/pojo/PersonalShift.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.ruoyi.staff.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;
/**
 * <p>
 *
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-05 03:52:19
 */
@Getter
@Setter
@TableName("personal_shift")
@ApiModel(value = "PersonalShift对象", description = "")
public class PersonalShift implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("班次(打卡规则id)")
    private Integer personalAttendanceLocationConfigId;
    @ApiModelProperty("员工在职id")
    private Long staffOnJobId;
    @ApiModelProperty("录入时间")
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    @ApiModelProperty("排班日期")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime workTime;
}
src/main/java/com/ruoyi/staff/service/PersonalShiftService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.ruoyi.staff.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.staff.dto.PerformanceShiftAddDto;
import com.ruoyi.staff.pojo.PersonalShift;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.Map;
/**
 * <p>
 *  æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-05 03:52:19
 */
public interface PersonalShiftService extends IService<PersonalShift> {
    int performanceShiftAdd(PerformanceShiftAddDto performanceShiftAddDto);
    Map<String, Object> performanceShiftPage(Page<Object> page, String time, String userName, Integer sysDeptId);
    void performanceShiftUpdate(PersonalShift performanceShift);
    IPage<Map<String, Object>> performanceShiftPageYear(Page<Object> page, String time, String userName, Integer sysDeptId);
    Map<Object, Object> exportToYearExcel(String time, String userName, Integer sysDeptId) throws Exception;
    Map<Object, Object> exportToMonthExcel(String time, String userName, Integer sysDeptId);
}
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java
@@ -1,5 +1,6 @@
package com.ruoyi.staff.service.impl;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
@@ -14,10 +15,12 @@
import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto;
import com.ruoyi.staff.dto.StaffOnJobDto;
import com.ruoyi.staff.mapper.PersonalAttendanceLocationConfigMapper;
import com.ruoyi.staff.mapper.PersonalShiftMapper;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper;
import com.ruoyi.staff.pojo.PersonalShift;
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.staff.service.PersonalAttendanceRecordsService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -63,6 +66,9 @@
    @Autowired
    private SysDeptMapper sysDeptMapper;
    @Autowired
    private PersonalShiftMapper personalShiftMapper;
    @Override
    public int add(PersonalAttendanceRecordsDto personalAttendanceRecordsDto) {
        // å½“前时间
@@ -76,20 +82,24 @@
        if (staffOnJob == null) {
            throw new BaseException("当前用户没有对应的员工信息");
        }
        /*判断打卡位置是否在规则范围内*/
        List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(Wrappers.<PersonalAttendanceLocationConfig>lambdaQuery()
                .eq(PersonalAttendanceLocationConfig::getSysDeptId, staffOnJob.getSysDeptId())
                .orderByDesc(PersonalAttendanceLocationConfig::getId));
        if (personalAttendanceLocationConfigs == null || personalAttendanceLocationConfigs.isEmpty()) {
            throw new BaseException("当前部门没有设置打卡规则");
        //判断判断员工当天是否有排班数据
        List<PersonalShift> personalShifts = personalShiftMapper.selectList(Wrappers.<PersonalShift>lambdaQuery()
                .eq(PersonalShift::getStaffOnJobId, staffOnJob.getId())
                .eq(PersonalShift::getWorkTime, currentDate.atStartOfDay())
                .isNotNull(PersonalShift::getPersonalAttendanceLocationConfigId));
        if (CollectionUtil.isEmpty(personalShifts)){
            throw new BaseException("当前用户当天没有排班数据");
        }
        /*判断打卡位置是否在规则范围内*/
        PersonalShift personalShift = personalShifts.get(0);
        PersonalAttendanceLocationConfig locationConfig = personalAttendanceLocationConfigMapper.selectById(personalShift.getPersonalAttendanceLocationConfigId());
        Double punchLongitude = personalAttendanceRecordsDto.getLongitude(); //打卡的经度
        Double punchLatitude = personalAttendanceRecordsDto.getLatitude(); // æ‰“卡的纬度
        if (punchLongitude == null || punchLatitude == null) {
            throw new BaseException("打卡失败:未获取到您的位置信息,请开启定位权限");
        }
        //计算打卡位置与考勤点的距离
        PersonalAttendanceLocationConfig locationConfig = personalAttendanceLocationConfigs.get(0);//获取最新的一条数据
        double allowedRadius = locationConfig.getRadius(); // å…è®¸çš„范围(米)
        double actualDistance = LocationUtils.calculateDistance(
                punchLatitude, punchLongitude, // å‘˜å·¥æ‰“卡的经纬度
@@ -231,12 +241,15 @@
        SysDept dept = sysDeptMapper.selectDeptById(staffOnJob.getSysDeptId());
        resultDto.setDeptName(dept != null ? dept.getDeptName() : null);
        //获取该员工对应的打卡规则
        List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(Wrappers.<PersonalAttendanceLocationConfig>lambdaQuery()
                .eq(PersonalAttendanceLocationConfig::getSysDeptId, staffOnJob.getSysDeptId())
                .orderByDesc(PersonalAttendanceLocationConfig::getId));
        if (personalAttendanceLocationConfigs.size()>0){
            resultDto.setStartAt(personalAttendanceLocationConfigs.get(0).getStartAt());
            resultDto.setEndAt(personalAttendanceLocationConfigs.get(0).getEndAt());
        List<PersonalShift> personalShifts = personalShiftMapper.selectList(Wrappers.<PersonalShift>lambdaQuery()
                .eq(PersonalShift::getStaffOnJobId, staffOnJob.getId())
                .eq(PersonalShift::getWorkTime, currentDate.atStartOfDay())
                .isNotNull(PersonalShift::getPersonalAttendanceLocationConfigId));
        if (CollectionUtil.isNotEmpty(personalShifts)){
            PersonalAttendanceLocationConfig personalAttendanceLocationConfig = personalAttendanceLocationConfigMapper.selectById(personalShifts.get(0).getPersonalAttendanceLocationConfigId());
            resultDto.setStartAt(personalAttendanceLocationConfig.getStartAt());
            resultDto.setEndAt(personalAttendanceLocationConfig.getEndAt());
            resultDto.setShift(personalAttendanceLocationConfig.getShift());
        }
        return resultDto;
    }
src/main/java/com/ruoyi/staff/service/impl/PersonalShiftServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,488 @@
package com.ruoyi.staff.service.impl;
import cn.hutool.core.date.DateTime;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.project.system.domain.SysDictData;
import com.ruoyi.staff.dto.PerformanceShiftAddDto;
import com.ruoyi.staff.dto.PerformanceShiftMapDto;
import com.ruoyi.staff.mapper.PersonalAttendanceLocationConfigMapper;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.pojo.PersonalShift;
import com.ruoyi.staff.mapper.PersonalShiftMapper;
import com.ruoyi.staff.service.PersonalAttendanceLocationConfigService;
import com.ruoyi.staff.service.PersonalShiftService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.staff.utils.JackSonUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import cn.hutool.core.date.DateUtil;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.ObjectUtils;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
/**
 * <p>
 *  æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-03-05 03:52:19
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class PersonalShiftServiceImpl extends ServiceImpl<PersonalShiftMapper, PersonalShift> implements PersonalShiftService {
    @Autowired
    private PersonalAttendanceLocationConfigMapper personalAttendanceLocationConfigMapper;
    @Override
    public int performanceShiftAdd(PerformanceShiftAddDto performanceShiftAddDto) {
        List<PersonalShift> 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.getStaffOnJobId().split(",");
        for (String userId : splitUserId) {
            //判断是否跨月
            boolean isMonth = startWeek.getMonthValue() != endWeek.getMonthValue();
            if (isMonth){
                //如果跨月,则两个月都判断一下看数据库是哪个月份的数据没有
                boolean exists1 = baseMapper.exists(Wrappers.<PersonalShift>lambdaQuery()
                        .eq(PersonalShift::getWorkTime, startWeek)
                        .eq(PersonalShift::getStaffOnJobId, userId));
                boolean exists2 = baseMapper.exists(Wrappers.<PersonalShift>lambdaQuery()
                        .eq(PersonalShift::getWorkTime, endWeek)
                        .eq(PersonalShift::getStaffOnJobId, 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);
                }
            }else {
                //不跨月
                boolean exists = baseMapper.exists(Wrappers.<PersonalShift>lambdaQuery()
                        .in(PersonalShift::getWorkTime, formattedDateTime)
                        .eq(PersonalShift::getStaffOnJobId, userId));
                // å¦‚果不存在添加数据
                if (!exists) {
                    list = saveMonth(performanceShiftAddDto.getEndWeek(), userId, list);
                }
            }
        }
        if (!list.isEmpty()) {
           saveBatch(list);
            list.clear();
        }
        // å†æ¬¡æ›´æ–°
        List<LocalDateTime> datesBetween = getLocalDateTimesBetween(performanceShiftAddDto.getStartWeek(), performanceShiftAddDto.getEndWeek());
        for (LocalDateTime date : datesBetween) {
            for (String s : splitUserId) {
                PersonalShift personalShift = new PersonalShift();
                personalShift.setPersonalAttendanceLocationConfigId(performanceShiftAddDto.getPersonalAttendanceLocationConfigId());
                personalShift.setStaffOnJobId(Long.valueOf(s));
                personalShift.setWorkTime(date);
                String formatterDateTime = date.format(formatter);
                baseMapper.update(new PersonalShift(), Wrappers.<PersonalShift>lambdaUpdate()
                        .set(PersonalShift::getPersonalAttendanceLocationConfigId, performanceShiftAddDto.getPersonalAttendanceLocationConfigId())
                        .eq(PersonalShift::getStaffOnJobId, s)
                        .eq(PersonalShift::getWorkTime, formatterDateTime));
            }
        }
        return 0;
    }
    @Override
    public Map<String, Object> performanceShiftPage(Page<Object> page, String time, String userName, Integer sysDeptId) {
        IPage<PerformanceShiftMapDto> mapIPage = baseMapper.performanceShiftPage(page, time, userName, sysDeptId);
        //查询所有班次(打卡规则)
        List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(null);
        mapIPage.getRecords().forEach(i -> {
            String[] shiftTimes = i.getShiftTime().split(";");
            double totalAttendance = 0;//总出勤天数
            List<Map<String, Object>> map = new ArrayList<>();
            // åˆ†å‰²æ—¥æœŸ
            for (String shiftTime : shiftTimes) {
                i.setShiftTime(null);
                Map<String, Object> hashMap = new HashMap<>();
                String[] shiftTimeAndShift = shiftTime.split(":");
                //排班详细数据
                hashMap.put("id", shiftTimeAndShift[2]);
                hashMap.put("shift", shiftTimeAndShift[1]);
                hashMap.put("time", shiftTimeAndShift[0]);
                map.add(hashMap);
                i.setList(map);
                //汇总的各班次统计数据
                for (PersonalAttendanceLocationConfig personalAttendanceLocationConfig : personalAttendanceLocationConfigs) {
                    if (!i.getMonthlyAttendance().containsKey(personalAttendanceLocationConfig.getShift())){
                        i.getMonthlyAttendance().put(personalAttendanceLocationConfig.getShift(), 0);
                    }
                    if (personalAttendanceLocationConfig.getShift().equals(shiftTimeAndShift[1])) {
                        BigDecimal bigDecimal = new BigDecimal(i.getMonthlyAttendance().get(personalAttendanceLocationConfig.getShift()).toString());
                        i.getMonthlyAttendance().put(personalAttendanceLocationConfig.getShift(), bigDecimal.add(new BigDecimal("1")));
                    }
                }
                //统计总出勤天数(早/中/晚/夜)都算出勤,其余都是休息
                if (shiftTimeAndShift[1].contains("早") ||
                        shiftTimeAndShift[1].contains("中") ||
                        shiftTimeAndShift[1].contains("晚") ||
                        shiftTimeAndShift[1].contains("夜")) {
                    i.getMonthlyAttendance().put("totalAttendance", totalAttendance += 1);
                }
            }
        });
        // èŽ·å–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);
        LocalDate firstDayOfMonth = localDateTime.toLocalDate().withDayOfMonth(1);
        LocalDate lastDayOfMonth = localDateTime.toLocalDate().with(TemporalAdjusters.lastDayOfMonth());
        List<LocalDateTime> localDateTimesBetween = getLocalDateTimesBetween(firstDayOfMonth.atStartOfDay(), lastDayOfMonth.atStartOfDay());
        List<Object> list1 = new ArrayList<>();
        for (LocalDateTime dateTime : localDateTimesBetween) {
            Map<Object, Object> hashMap = new HashMap<>();
            DateTime parse = DateUtil.parse(dateTime.format(formatter));
            hashMap.put("weekly", DateUtil.weekOfYear(DateUtil.offsetDay(parse, 1)));
            hashMap.put("headerTime", getWeek(dateTime.format(formatters)));
            list1.add(hashMap);
        }
        Map<String, Object> resultMap = new HashMap<>();
        resultMap.put("page", mapIPage);
        resultMap.put("headerList", list1);
        return resultMap;
    }
    @Override
    public void performanceShiftUpdate(PersonalShift personalShift) {
        baseMapper.update(new PersonalShift(), Wrappers.<PersonalShift>lambdaUpdate()
                .eq(PersonalShift::getId, personalShift.getId())
                .set(PersonalShift::getPersonalAttendanceLocationConfigId, personalShift.getPersonalAttendanceLocationConfigId()));
    }
    @Override
    public IPage<Map<String, Object>> performanceShiftPageYear(Page<Object> page, String time, String userName, Integer sysDeptId) {
        IPage<Map<String, Object>> mapYearIPage = baseMapper.performanceShiftYear(page, time, userName, sysDeptId);
        //查询所有班次(打卡规则)
        List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(null);
        mapYearIPage.setRecords(annualAttendanceProcessing(mapYearIPage.getRecords(), personalAttendanceLocationConfigs));
        return mapYearIPage;
    }
    @Override
    public Map<Object, Object> exportToYearExcel(String time, String userName, Integer sysDeptId) throws Exception {
        Map<Object, Object> map = new HashMap<>();
        //查询所有班次(打卡规则)
        List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(null);
        DateTimeFormatter formatters = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        // å°†å­—符串时间转换为 LocalDateTime ç±»åž‹æ—¶é—´
        LocalDateTime localDateTime = LocalDateTime.parse(time, formatters);
        map.put("header", getYearHeader(localDateTime.getYear() + " å¹´", personalAttendanceLocationConfigs));
        List<Map<String, Object>> mapYearList = baseMapper.performanceShiftYearList(time, userName, sysDeptId);
        annualAttendanceProcessing(mapYearList, personalAttendanceLocationConfigs);
        List<List<Object>> lists = dataRequiredForProcessingIntoExcel(mapYearList, personalAttendanceLocationConfigs);
        map.put("data", lists);
        return map;
    }
    @Override
    public Map<Object, Object> exportToMonthExcel(String time, String userName, Integer sysDeptId) {
        //查询所有班次(打卡规则)
        List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(null);
        List<PerformanceShiftMapDto> mapIPage = baseMapper.performanceShiftList(time, userName, sysDeptId);
        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 (PersonalAttendanceLocationConfig personalAttendanceLocationConfig : personalAttendanceLocationConfigs) {
                    if (!i.getMonthlyAttendance().containsKey(personalAttendanceLocationConfig.getShift())) {
                        i.getMonthlyAttendance().put(personalAttendanceLocationConfig.getShift(), 0);
                    }
                    if (personalAttendanceLocationConfig.getShift().equals(shiftTimeAndShift[1])) {
                        BigDecimal bigDecimal = new BigDecimal(i.getMonthlyAttendance().get(personalAttendanceLocationConfig.getShift()).toString());
                        i.getMonthlyAttendance().put(personalAttendanceLocationConfig.getShift(), bigDecimal.add(new BigDecimal("1")));
                    }
                }
                //统计总出勤天数(早/中/晚/夜)都算出勤,其余都是休息
                if (shiftTimeAndShift[1].contains("早") ||
                        shiftTimeAndShift[1].contains("中") ||
                        shiftTimeAndShift[1].contains("晚") ||
                        shiftTimeAndShift[1].contains("夜")) {
                    i.getMonthlyAttendance().put("totalAttendance", totalAttendance += 1);
                }
                hashMap.put("id", shiftTimeAndShift[2]);
                hashMap.put("shift", shiftTimeAndShift[1]);
                hashMap.put("time", shiftTimeAndShift[0]);
                map.add(hashMap);
            }
            i.setList(map);
            i.setShiftTime(null);
        });
        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));
        List<List<Object>> lists = dataRequiredForProcessingIntoExcelMonth(mapIPage, personalAttendanceLocationConfigs);
        map.put("data", lists);
        return map;
    }
    // å¹´åˆ†é¡µä¸Žå¯¼å‡ºå…±åŒä½¿ç”¨
    public List<Map<String, Object>> annualAttendanceProcessing(List<Map<String, Object>> mapYearList,List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs) {
        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 (PersonalAttendanceLocationConfig personalAttendanceLocationConfig : personalAttendanceLocationConfigs) {
                    // åˆå§‹åŒ–赋值
                    if (!hashMapYear.containsKey(personalAttendanceLocationConfig.getShift())){
                        hashMapYear.put(personalAttendanceLocationConfig.getShift(), 0);
                    }
                    // æœˆ
                    if (!ObjectUtils.isEmpty(map.get("month_str"))) {
                        String charArray = map.get("month_str").toString();
                        int count = countOccurrences(charArray, i + ":" + personalAttendanceLocationConfig.getShift());
                        hashMapMonth.put(personalAttendanceLocationConfig.getShift(), count);
                        hashMapYear.put(personalAttendanceLocationConfig.getShift(), new BigDecimal(hashMapYear.get(personalAttendanceLocationConfig.getShift()).toString()).add(new BigDecimal(count)));
                        // æ—©ï¼Œä¸­ï¼Œå¤œï¼Œå·®
                        if (personalAttendanceLocationConfig.getShift().contains("早") ||
                                personalAttendanceLocationConfig.getShift().contains("中") ||
                                personalAttendanceLocationConfig.getShift().contains("晚") ||
                                personalAttendanceLocationConfig.getShift().contains("夜")) {
                            totalMonthAttendance += count;
                            totalYearAttendance += count;
                        }
                    }
                    // ç©ºæ•°æ®
                    else {
                        map.put("work_time", i);
                        hashMapMonth.put(personalAttendanceLocationConfig.getShift(), 0);
                    }
                }
                hashMapMonth.put("totalMonthAttendance", totalMonthAttendance);
                hashMapYear.put("totalYearAttendance", totalYearAttendance);
                resultMap.put(i + "", hashMapMonth);
            }
            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<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs) 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"));
            personalAttendanceLocationConfigs.forEach(j -> {
                excelRowList.add(year.get(j.getShift()));
            });
            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 (PersonalAttendanceLocationConfig personalAttendanceLocationConfig : personalAttendanceLocationConfigs) {
                        excelRowList.add(month.get(j + "").get(personalAttendanceLocationConfig.getShift()));
                }
            }
            data.add(excelRowList);
        }
        return data;
    }
    private List<PersonalShift>   saveMonth (LocalDateTime week,String userId,List<PersonalShift> list){
        LocalDate firstDayOfMonth = week.toLocalDate().withDayOfMonth(1);
        LocalDate lastDayOfMonth = week.toLocalDate().with(TemporalAdjusters.lastDayOfMonth());
        List<LocalDateTime> localDateTimesBetween = getLocalDateTimesBetween(firstDayOfMonth.atStartOfDay(), lastDayOfMonth.atStartOfDay());
        localDateTimesBetween.forEach(i -> {
            PersonalShift personalShift = new PersonalShift();
            personalShift.setStaffOnJobId(Long.valueOf(userId));
            personalShift.setWorkTime(i);
            list.add(personalShift);
            if (list.size() >= 1000) {
                saveBatch(list);
                list.clear();
            }
        });
        return list;
    }
    // èŽ·å–ä¸¤ä¸ªlocalDateTime的每一天
    public static List<LocalDateTime> getLocalDateTimesBetween(LocalDateTime start, LocalDateTime end) {
        List<LocalDateTime> localDateTimes = new ArrayList<>();
        LocalDate currentDate = start.toLocalDate();
        LocalDateTime currentLocalDateTime = start;
        while (!currentDate.isAfter(end.toLocalDate())) {
            localDateTimes.add(currentLocalDateTime);
            currentLocalDateTime = currentLocalDateTime.plusDays(1);
            currentDate = currentDate.plusDays(1);
        }
        return localDateTimes;
    }
    public static String getWeek(String dayStr) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try {
            Date date = sdf.parse(dayStr);
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
            int day = calendar.get(Calendar.DAY_OF_MONTH);
            return day + " " + getWeekDay(dayOfWeek);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    public static String getWeekDay(int dayOfWeek) {
        switch (dayOfWeek) {
            case Calendar.MONDAY:
                return "周一";
            case Calendar.TUESDAY:
                return "周二";
            case Calendar.WEDNESDAY:
                return "周三";
            case Calendar.THURSDAY:
                return "周四";
            case Calendar.FRIDAY:
                return "周五";
            case Calendar.SATURDAY:
                return "周六";
            case Calendar.SUNDAY:
                return "周日";
            default:
                return "未知";
        }
    }
    /**
     * è¿”回表头
     * <p>
     * å¤–层List代表行内层 List代表列  ç›¸åŒçš„列数据会被主动合并
     * æž„造双列表头
     *
     * @return List<List < String>>
     */
    private static List<List<String>> getYearHeader(String year, List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs) {
        List<List<String>> line = new ArrayList<>();
        line.add(Arrays.asList("考勤汇总", "序号", "序号"));
        line.add(Arrays.asList("考勤汇总", "工号", "工号"));
        line.add(Arrays.asList("考勤汇总", "姓名", "姓名"));
        line.add(Arrays.asList("出勤详情", year, "出勤"));
        // å¹´ header
        for (PersonalAttendanceLocationConfig personalAttendanceLocationConfig : personalAttendanceLocationConfigs) {
                line.add(Arrays.asList("考勤汇总", year, personalAttendanceLocationConfig.getShift()));
        }
        // æœˆheader
        for (int i = 1; i < 13; i++) {
            line.add(Arrays.asList("出勤详情", i + " æœˆ", "出勤"));
            for (PersonalAttendanceLocationConfig personalAttendanceLocationConfig : personalAttendanceLocationConfigs) {
                line.add(Arrays.asList("出勤详情", i + " æœˆ", personalAttendanceLocationConfig.getShift()));
            }
        }
        return line;
    }
    private static List<List<String>> getMonthHeader(LocalDateTime localDateTimeYear) {
        String year = localDateTimeYear.getYear() + " å¹´äººå‘˜ç­æ¬¡";
        List<List<String>> line = new ArrayList<>();
        line.add(Arrays.asList(year, "序号", "序号", "序号"));
        line.add(Arrays.asList(year, "姓名", "姓名", "姓名"));
        line.add(Arrays.asList(year, "部门", "部门", "部门"));
        line.add(Arrays.asList(year, localDateTimeYear.getYear() + "", localDateTimeYear.getYear() + "", "出勤"));
        line.add(Arrays.asList(year, localDateTimeYear.getYear() + "", localDateTimeYear.getYear() + "","休"));
        line.add(Arrays.asList(year, "å¹´", "å¹´", "请假"));
        line.add(Arrays.asList(year, localDateTimeYear.getMonthValue() + "", localDateTimeYear.getMonthValue() + "", "早"));
        line.add(Arrays.asList(year, "月", "月", "中"));
        line.add(Arrays.asList(year, "", "", "夜"));
        line.add(Arrays.asList(year, "周次", "星期", "出差"));
        LocalDate firstDayOfMonth = localDateTimeYear.toLocalDate().withDayOfMonth(1);
        LocalDate lastDayOfMonth = localDateTimeYear.toLocalDate().with(TemporalAdjusters.lastDayOfMonth());
        List<LocalDateTime> timeList = getLocalDateTimesBetween(firstDayOfMonth.atStartOfDay(), lastDayOfMonth.atStartOfDay());
        timeList.forEach(i -> {
            int dayOfYear = i.getDayOfMonth();
            Date from = Date.from(i.atZone(ZoneId.systemDefault()).toInstant());
            String weekDay = getWeekDay(i.getDayOfWeek().getValue());
            line.add(Arrays.asList(year, DateUtil.weekOfYear(DateUtil.offsetDay(from, 1)) + "", weekDay, dayOfYear + ""));
        });
        return line;
    }
    public List<List<Object>> dataRequiredForProcessingIntoExcelMonth(List<PerformanceShiftMapDto> list, List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs) {
        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).getName());
            excelRowList.add(list.get(i).getDepartment());
            excelRowList.add(list.get(i).getMonthlyAttendance().get("totalAttendance"));
            excelRowList.add(list.get(i).getMonthlyAttendance().get("%休%")); // ä¼‘
            excelRowList.add(list.get(i).getMonthlyAttendance().get("%假%")); // å‡
            excelRowList.add(list.get(i).getMonthlyAttendance().get("%早%")); // æ—©
            excelRowList.add(list.get(i).getMonthlyAttendance().get("%中%")); // ä¸­
            excelRowList.add(list.get(i).getMonthlyAttendance().get("%夜%")); // å¤œ
            excelRowList.add(list.get(i).getMonthlyAttendance().get("%å·®%")); // å·®
            for (Map<String, Object> o : list.get(i).getList()) {
                String enumLabel = "";
                for (PersonalAttendanceLocationConfig personalAttendanceLocationConfig : personalAttendanceLocationConfigs) {
                    if (personalAttendanceLocationConfig.getShift().equals(o.get("shift"))) {
                        enumLabel = personalAttendanceLocationConfig.getShift();
                    }
                }
                excelRowList.add(ObjectUtils.isEmpty(enumLabel) ? "-" : enumLabel);
            }
            data.add(excelRowList);
        }
        return data;
    }
}
src/main/java/com/ruoyi/staff/task/PersonalAttendanceRecordsTask.java
@@ -1,9 +1,17 @@
package com.ruoyi.staff.task;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.staff.dto.PerformanceShiftAddDto;
import com.ruoyi.staff.mapper.PersonalAttendanceLocationConfigMapper;
import com.ruoyi.staff.mapper.PersonalShiftMapper;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.staff.service.PersonalAttendanceRecordsService;
import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper;
import com.ruoyi.staff.service.PersonalShiftService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
@@ -12,7 +20,14 @@
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.time.temporal.WeekFields;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Collectors;
/**
 * ä¸ªäººè€ƒå‹¤è®°å½•定时任务
@@ -29,6 +44,15 @@
    @Autowired
    private PersonalAttendanceRecordsService personalAttendanceRecordsService;
    @Autowired
    private PersonalAttendanceLocationConfigMapper personalAttendanceLocationConfigMapper;
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
    @Autowired
    private PersonalShiftService personalShiftService;
    /**
     * æ¯å¤©å‡Œæ™¨ç”Ÿæˆæ˜¨æ—¥çš„缺勤记录
@@ -73,4 +97,50 @@
            log.error("生成昨日缺勤记录任务执行失败:{}", e.getMessage(), e);
        }
    }
    /**
     * å®šæ—¶ä»»åŠ¡,每个月1号的00:00:00给下一个月排班
     * ç»™æ¯ä¸ªäººéƒ½è¿›è¡ŒæŽ’班(默认早班)
     */
    @Scheduled(cron = "0 10 0 1 * ?")
    private void timerCreateSchedule() {
        log.info("开始给每个人进行排班,默认早班======start");
        PerformanceShiftAddDto performanceShiftAddDto = new PerformanceShiftAddDto();
        //查询所有班次(打卡规则)
        List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(null);
        if (CollectionUtil.isEmpty(personalAttendanceLocationConfigs)){
            return;
        }
        performanceShiftAddDto.setPersonalAttendanceLocationConfigId(personalAttendanceLocationConfigs.get(0).getId());
        //在职人员
        List<StaffOnJob> staffOnJobs = staffOnJobMapper.selectList(Wrappers.<StaffOnJob>lambdaQuery().eq(StaffOnJob::getStaffState, 1));
        if (CollectionUtil.isEmpty(staffOnJobs)){
            return;
        }
        String userIds = staffOnJobs.stream().map(user -> user.getId().toString()).distinct().collect(Collectors.joining(","));
        performanceShiftAddDto.setStaffOnJobId(userIds);
        //周次--当月所有
        // èŽ·å–å½“å‰æ—¥æœŸ
        LocalDate today = LocalDate.now(ZoneId.of(ZoneId.SHORT_IDS.get("CTT"))).plusMonths(1);
        // èŽ·å–æœ¬æœˆçš„ç¬¬ä¸€å¤©å’Œæœ€åŽä¸€å¤©
        LocalDate firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth());
        LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth());
        // èŽ·å–å‘¨å­—æ®µä¿¡æ¯ï¼ˆæ ¹æ®åŒºåŸŸè®¾ç½®ï¼‰
        WeekFields weekFields = WeekFields.of(Locale.getDefault());
        // èŽ·å–æœ¬æœˆç¬¬ä¸€å¤©çš„å‘¨ä¸€
        LocalDate startOfWeek = firstDayOfMonth.with(TemporalAdjusters.previousOrSame(weekFields.getFirstDayOfWeek()));
        // éåŽ†æœ¬æœˆæ‰€æœ‰å¤©æ•°ï¼Œæ‰¾å‡ºæ¯å‘¨çš„ç¬¬ä¸€å¤©å’Œæœ€åŽä¸€å¤©
        LocalDate endOfWeek;
        while (startOfWeek.isBefore(firstDayOfMonth.plusMonths(1))) {
            endOfWeek = startOfWeek.plusDays(6);
            LocalDateTime startDateTime = LocalDateTime.of(startOfWeek, LocalTime.MIDNIGHT);
            LocalDateTime endDateTime = LocalDateTime.of(endOfWeek, LocalTime.MIDNIGHT);
            log.info("Week starts on {} and ends on {}", startDateTime, endDateTime);
            performanceShiftAddDto.setStartWeek(startDateTime);
            performanceShiftAddDto.setEndWeek(endDateTime);
            personalShiftService.performanceShiftAdd(performanceShiftAddDto);
            startOfWeek = startOfWeek.plusWeeks(1);
        }
        log.info("排班结束======end");
    }
}
src/main/java/com/ruoyi/staff/utils/JackSonUtil.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,133 @@
package com.ruoyi.staff.utils;
import com.fasterxml.jackson.core.JsonGenerationException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ObjectWriter;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.stereotype.Component;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
 * JSON解析处理
 *
 * @author zss
 */
@Component
public class JackSonUtil {
    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
    private static final ObjectWriter OBJECT_WRITER = OBJECT_MAPPER.writerWithDefaultPrettyPrinter();
    public static void marshal(File file, Object value) throws Exception {
        try {
            OBJECT_WRITER.writeValue(file, value);
        } catch (JsonGenerationException e) {
            throw new Exception(e);
        } catch (JsonMappingException e) {
            throw new Exception(e);
        } catch (IOException e) {
            throw new Exception(e);
        }
    }
    public static void marshal(OutputStream os, Object value) throws Exception {
        try {
            OBJECT_WRITER.writeValue(os, value);
        } catch (JsonGenerationException e) {
            throw new Exception(e);
        } catch (JsonMappingException e) {
            throw new Exception(e);
        } catch (IOException e) {
            throw new Exception(e);
        }
    }
    public static String marshal(Object value) throws Exception {
        try {
            return OBJECT_WRITER.writeValueAsString(value);
        } catch (JsonGenerationException e) {
            throw new Exception(e);
        } catch (JsonMappingException e) {
            throw new Exception(e);
        } catch (IOException e) {
            throw new Exception(e);
        }
    }
    public static byte[] marshalBytes(Object value) throws Exception {
        try {
            return OBJECT_WRITER.writeValueAsBytes(value);
        } catch (JsonGenerationException e) {
            throw new Exception(e);
        } catch (JsonMappingException e) {
            throw new Exception(e);
        } catch (IOException e) {
            throw new Exception(e);
        }
    }
    public static <T> T unmarshal(File file, Class<T> valueType) throws Exception {
        try {
            return OBJECT_MAPPER.readValue(file, valueType);
        } catch (JsonParseException e) {
            throw new Exception(e);
        } catch (JsonMappingException e) {
            throw new Exception(e);
        } catch (IOException e) {
            throw new Exception(e);
        }
    }
    public static <T> T unmarshal(InputStream is, Class<T> valueType) throws Exception {
        try {
            return OBJECT_MAPPER.readValue(is, valueType);
        } catch (JsonParseException e) {
            throw new Exception(e);
        } catch (JsonMappingException e) {
            throw new Exception(e);
        } catch (IOException e) {
            throw new Exception(e);
        }
    }
    /**
     * å­—符串转对象
     * @param str
     * @param valueType
     * @return
     * @param <T>
     * @throws Exception
     */
    public static <T> T unmarshal(String str, Class<T> valueType) throws Exception {
        try {
            OBJECT_MAPPER.registerModule(new JavaTimeModule());
            return OBJECT_MAPPER.readValue(str, valueType);
        } catch (JsonParseException e) {
            throw new Exception(e);
        } catch (JsonMappingException e) {
            throw new Exception(e);
        } catch (IOException e) {
            throw new Exception(e);
        }
    }
    public static <T> T unmarshal(byte[] bytes, Class<T> valueType) throws Exception {
        try {
            if (bytes == null) {
                bytes = new byte[0];
            }
            return OBJECT_MAPPER.readValue(bytes, 0, bytes.length, valueType);
        } catch (JsonParseException e) {
            throw new Exception(e);
        } catch (JsonMappingException e) {
            throw new Exception(e);
        } catch (IOException e) {
            throw new Exception(e);
        }
    }
}
src/main/java/com/ruoyi/staff/utils/StyleMonthUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,86 @@
package com.ruoyi.staff.utils;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
public class StyleMonthUtils {
    /**
     * æ ‡é¢˜æ ·å¼
     * @return
     */
    public static WriteCellStyle getHeadStyle(){
        // å¤´çš„ç­–ç•¥
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // èƒŒæ™¯é¢œè‰²
//        headWriteCellStyle.setFillForegroundColor(IndexedColors.WHITE.getIndex());
//        headWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        // å­—体
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontName("等线");//设置字体名字
        headWriteFont.setFontHeightInPoints((short)9);//设置字体大小
        headWriteFont.setBold(true);//字体加粗
        headWriteFont.setColor((short) 0);
        headWriteCellStyle.setWriteFont(headWriteFont); //在样式用应用设置的字体;
        // æ ·å¼
        headWriteCellStyle.setBorderBottom(BorderStyle.THIN);//设置底边框;
        headWriteCellStyle.setBottomBorderColor((short) 0);//设置底边框颜色;
        headWriteCellStyle.setBorderLeft(BorderStyle.THIN);  //设置左边框;
        headWriteCellStyle.setLeftBorderColor((short) 0);//设置左边框颜色;
        headWriteCellStyle.setBorderRight(BorderStyle.THIN);//设置右边框;
        headWriteCellStyle.setRightBorderColor((short) 0);//设置右边框颜色;
        headWriteCellStyle.setBorderTop(BorderStyle.THIN);//设置顶边框;
        headWriteCellStyle.setTopBorderColor((short) 0); //设置顶边框颜色;
        headWriteCellStyle.setWrapped(false);  //设置自动换行;
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//设置水平对齐的样式为居中对齐;
        headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);  //设置垂直对齐的样式为居中对齐;
        headWriteCellStyle.setShrinkToFit(true);//设置文本收缩至合适
        return headWriteCellStyle;
    }
    /**
     * å†…容样式
     * @return
     */
    public static WriteCellStyle getContentStyle(){
        // å†…容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // è¿™é‡Œéœ€è¦æŒ‡å®š FillPatternType ä¸ºFillPatternType.SOLID_FOREGROUND ä¸ç„¶æ— æ³•显示背景颜色.头默认了 FillPatternType所以可以不指定
//        contentWriteCellStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
        contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        // è®¾ç½®å­—体
        WriteFont contentWriteFont = new WriteFont();
        contentWriteFont.setFontHeightInPoints((short) 10);//设置字体大小
        contentWriteFont.setFontName("等线"); //设置字体名字
        contentWriteCellStyle.setWriteFont(contentWriteFont);//在样式用应用设置的字体;
        //设置样式;
        contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);//设置底边框;
        contentWriteCellStyle.setBottomBorderColor((short) 0);//设置底边框颜色;
        contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);  //设置左边框;
        contentWriteCellStyle.setLeftBorderColor((short) 0);//设置左边框颜色;
        contentWriteCellStyle.setBorderRight(BorderStyle.THIN);//设置右边框;
        contentWriteCellStyle.setRightBorderColor((short) 0);//设置右边框颜色;
        contentWriteCellStyle.setBorderTop(BorderStyle.THIN);//设置顶边框;
        contentWriteCellStyle.setTopBorderColor((short) 0); ///设置顶边框颜色;
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);// æ°´å¹³å±…中
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);// åž‚直居中
        contentWriteCellStyle.setWrapped(false); //设置自动换行;
//        contentWriteCellStyle.setShrinkToFit(true);//设置文本收缩至合适
        return contentWriteCellStyle;
    }
}
src/main/java/com/ruoyi/staff/utils/StyleYearUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,89 @@
package com.ruoyi.staff.utils;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.VerticalAlignment;
public class StyleYearUtils {
    /**
     * æ ‡é¢˜æ ·å¼
     *
     * @return
     */
    public static WriteCellStyle getHeadStyle() {
        // å¤´çš„ç­–ç•¥
        WriteCellStyle headWriteCellStyle = new WriteCellStyle();
        // èƒŒæ™¯é¢œè‰²
//        headWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());
        headWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        // å­—体
        WriteFont headWriteFont = new WriteFont();
        headWriteFont.setFontName("仿宋");//设置字体名字
        headWriteFont.setFontHeightInPoints((short) 9);//设置字体大小
        headWriteFont.setBold(true);//字体加粗
        headWriteFont.setColor((short) 1);
        headWriteCellStyle.setWriteFont(headWriteFont); //在样式用应用设置的字体;
        // æ ·å¼
        headWriteCellStyle.setBorderBottom(BorderStyle.THIN);//设置底边框;
        headWriteCellStyle.setBottomBorderColor((short) 1);//设置底边框颜色;
        headWriteCellStyle.setBorderLeft(BorderStyle.THIN);  //设置左边框;
        headWriteCellStyle.setLeftBorderColor((short) 1);//设置左边框颜色;
        headWriteCellStyle.setBorderRight(BorderStyle.THIN);//设置右边框;
        headWriteCellStyle.setRightBorderColor((short) 1);//设置右边框颜色;
        headWriteCellStyle.setBorderTop(BorderStyle.THIN);//设置顶边框;
        headWriteCellStyle.setTopBorderColor((short) 1); //设置顶边框颜色;
        headWriteCellStyle.setWrapped(false);  //设置自动换行;
        headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);//设置水平对齐的样式为居中对齐;
        headWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);  //设置垂直对齐的样式为居中对齐;
        headWriteCellStyle.setShrinkToFit(true);//设置文本收缩至合适
        return headWriteCellStyle;
    }
    /**
     * å†…容样式
     *
     * @return
     */
    public static WriteCellStyle getContentStyle() {
        // å†…容的策略
        WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
        // è¿™é‡Œéœ€è¦æŒ‡å®š FillPatternType ä¸ºFillPatternType.SOLID_FOREGROUND ä¸ç„¶æ— æ³•显示背景颜色.头默认了 FillPatternType所以可以不指定
//        contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
        contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
        // è®¾ç½®å­—体
        WriteFont contentWriteFont = new WriteFont();
        contentWriteFont.setFontHeightInPoints((short) 9);//设置字体大小
        contentWriteFont.setFontName("仿宋"); //设置字体名字
        contentWriteCellStyle.setWriteFont(contentWriteFont);//在样式用应用设置的字体;
        //设置样式;
        contentWriteCellStyle.setBorderBottom(BorderStyle.THIN);//设置底边框;
        contentWriteCellStyle.setBottomBorderColor((short) 0);//设置底边框颜色;
        contentWriteCellStyle.setBorderLeft(BorderStyle.THIN);  //设置左边框;
        contentWriteCellStyle.setLeftBorderColor((short) 0);//设置左边框颜色;
        contentWriteCellStyle.setBorderRight(BorderStyle.THIN);//设置右边框;
        contentWriteCellStyle.setRightBorderColor((short) 0);//设置右边框颜色;
        contentWriteCellStyle.setBorderTop(BorderStyle.THIN);//设置顶边框;
        contentWriteCellStyle.setTopBorderColor((short) 0); ///设置顶边框颜色;
        contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);// æ°´å¹³å±…中
        contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);// åž‚直居中
        contentWriteCellStyle.setWrapped(false); //设置自动换行;
//        contentWriteCellStyle.setShrinkToFit(true);//设置文本收缩至合适
        return contentWriteCellStyle;
    }
}
src/main/resources/mapper/staff/PersonalShiftMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,106 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.staff.mapper.PersonalShiftMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.staff.pojo.PersonalShift">
        <id column="id" property="id" />
        <result column="personal_attendance_location_config_id" property="personalAttendanceLocationConfigId" />
        <result column="staff_on_job_id" property="staffOnJobId" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
        <result column="work_time" property="workTime" />
    </resultMap>
    <select id="performanceShiftPage" resultType="com.ruoyi.staff.dto.PerformanceShiftMapDto">
         SELECT
        u.staff_name name,
        GROUP_CONCAT(s.work_time, ':', IFNULL(palc.shift, ''), ':', s.id order by s.work_time SEPARATOR ';') AS shift_time,
                u.id user_id
        FROM personal_shift s
        LEFT JOIN staff_on_job u ON u.id = s.staff_on_job_id
        LEFT JOIN personal_attendance_location_config palc ON palc.id = s.personal_attendance_location_config_id
        <where>
             <if test="sysDeptId != null and sysDeptId != ''">
                and u.sys_dept_id = #{sysDeptId}
            </if>
            <if test="time != null and time != ''">
                and DATE_FORMAT(s.work_time, '%Y-%m') = DATE_FORMAT(#{time}, '%Y-%m' )
            </if>
            <if test="userName != null and userName != ''">
                and u.staff_name like concat('%', #{userName}, '%')
            </if>
        </where>
        GROUP BY u.id
        order by s.create_time
    </select>
    <select id="performanceShiftYear" resultType="java.util.Map">
         SELECT
         u.staff_name name,
         u.id user_id,
         u.staff_no account,
        DATE_FORMAT(s.work_time, '%c') work_time,
        GROUP_CONCAT(DATE_FORMAT(s.work_time, '%c'), ':', IFNULL(palc.shift, '') order by s.work_time SEPARATOR ';') month_str
        FROM personal_shift s
        LEFT JOIN staff_on_job u ON u.id = s.staff_on_job_id
         LEFT JOIN personal_attendance_location_config palc ON palc.id = s.personal_attendance_location_config_id
         <where>
            <if test="time != null and time != ''">
                and DATE_FORMAT(s.work_time, '%Y') = DATE_FORMAT(#{time}, '%Y' )
            </if>
            <if test="userName != null and userName != ''">
                and u.staff_name like concat('%', #{userName}, '%')
            </if>
             <if test="sysDeptId != null and sysDeptId != ''">
                and u.sys_dept_id = #{sysDeptId}
            </if>
          </where>
        GROUP BY u.id
        order by s.create_time
    </select>
    <select id="performanceShiftYearList" resultType="java.util.Map">
         SELECT
        u.staff_name name,
        s.staff_on_job_id user_id, u.staff_no account,
        DATE_FORMAT(s.work_time, '%c') work_time,
        GROUP_CONCAT(DATE_FORMAT(s.work_time, '%c'), ':', IFNULL(palc.shift, '') order by s.work_time SEPARATOR ';') month_str
        FROM personal_shift s
        LEFT JOIN staff_on_job u ON u.id = s.staff_on_job_id
        LEFT JOIN personal_attendance_location_config palc ON palc.id = s.personal_attendance_location_config_id
       <where>
        <if test="time != null and time != ''">
            and DATE_FORMAT(s.work_time, '%Y') = DATE_FORMAT(#{time}, '%Y' )
        </if>
        <if test="userName != null and userName != ''">
            and u.staff_name like concat('%', #{userName}, '%')
        </if>
       <if test="sysDeptId != null and sysDeptId != ''">
            and u.sys_dept_id = #{sysDeptId}
       </if>
          </where>
        GROUP BY u.id
        order by s.create_time
    </select>
    <select id="performanceShiftList" resultType="com.ruoyi.staff.dto.PerformanceShiftMapDto">
         SELECT
        u.staff_name name,
        GROUP_CONCAT(s.work_time, ':', IFNULL(palc.shift, ''), ':', s.id order by s.work_time SEPARATOR ';') AS shift_time, u.id user_id
        FROM personal_shift s
        LEFT JOIN staff_on_job u ON u.id = s.staff_on_job_id
        LEFT JOIN personal_attendance_location_config palc ON palc.id = s.personal_attendance_location_config_id
        <where>
            <if test="time != null and time != ''">
                and DATE_FORMAT(s.work_time, '%Y-%m') = DATE_FORMAT(#{time}, '%Y-%m' )
            </if>
            <if test="userName != null and userName != ''">
                and u.staff_name like concat('%', #{userName}, '%')
            </if>
             <if test="sysDeptId != null and sysDeptId != ''">
                and u.sys_dept_id = #{sysDeptId}
            </if>
        </where>
        GROUP BY u.id
        order by s.create_time
    </select>
</mapper>