考勤
This commit is contained in:
		@ -2,6 +2,7 @@ package org.dromara.cailiaoshebei.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.bean.BeanUtil;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 | 
			
		||||
import jakarta.annotation.Resource;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.cailiaoshebei.domain.BusCailiaoshebeiPici;
 | 
			
		||||
import org.dromara.cailiaoshebei.domain.bo.BusMaterialbatchdemandplanAddReq;
 | 
			
		||||
@ -19,6 +20,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.context.annotation.Lazy;
 | 
			
		||||
import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		||||
import org.springframework.context.annotation.Lazy;
 | 
			
		||||
import org.springframework.context.event.EventListener;
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import lombok.NoArgsConstructor;
 | 
			
		||||
import org.dromara.project.domain.BusAttendance;
 | 
			
		||||
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
@ -21,7 +22,7 @@ public class SubConstructionUserAttendanceByDay {
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private Date upClockTime;
 | 
			
		||||
    private LocalDateTime upClockTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班打卡图片id
 | 
			
		||||
@ -45,7 +46,7 @@ public class SubConstructionUserAttendanceByDay {
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private Date downClockTime;
 | 
			
		||||
    private LocalDateTime downClockTime;
 | 
			
		||||
 | 
			
		||||
    public static SubConstructionUserAttendanceByDay build(List<BusAttendance> attendanceList) {
 | 
			
		||||
        if (attendanceList == null) {
 | 
			
		||||
@ -53,12 +54,12 @@ public class SubConstructionUserAttendanceByDay {
 | 
			
		||||
        }
 | 
			
		||||
        SubConstructionUserAttendanceByDay constructionUserAttendanceByDay = new SubConstructionUserAttendanceByDay();
 | 
			
		||||
        for (BusAttendance attendance : attendanceList) {
 | 
			
		||||
            if (attendance.getCommuter().equals(BusAttendanceCommuterEnum.CLOCKIN.getValue())) {
 | 
			
		||||
            if (attendance.getClockType().equals(BusAttendanceCommuterEnum.CLOCKIN.getValue())) {
 | 
			
		||||
                constructionUserAttendanceByDay.setUpClockTime(attendance.getClockTime());
 | 
			
		||||
                if (attendance.getFacePic() != null) {
 | 
			
		||||
                    constructionUserAttendanceByDay.setUpClockPicId(Long.valueOf(attendance.getFacePic()));
 | 
			
		||||
                }
 | 
			
		||||
            } else if (attendance.getCommuter().equals(BusAttendanceCommuterEnum.CLOCKOUT.getValue())) {
 | 
			
		||||
            } else if (attendance.getClockType().equals(BusAttendanceCommuterEnum.CLOCKOUT.getValue())) {
 | 
			
		||||
                constructionUserAttendanceByDay.setDownClockTime(attendance.getClockTime());
 | 
			
		||||
                if (attendance.getFacePic() != null) {
 | 
			
		||||
                    constructionUserAttendanceByDay.setDownClockPicId(Long.valueOf(attendance.getFacePic()));
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -28,7 +29,7 @@ public class SubConstructionUserAttendanceMonthVo implements Serializable {
 | 
			
		||||
    @JsonFormat(shape = JsonFormat.Shape.STRING,
 | 
			
		||||
        pattern = "yyyy-MM-dd",
 | 
			
		||||
        timezone = "GMT+8")
 | 
			
		||||
    private Date clockDate;
 | 
			
		||||
    private LocalDate clockDate;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 当天打卡状态
 | 
			
		||||
 | 
			
		||||
@ -65,6 +65,7 @@ import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.net.URLEncoder;
 | 
			
		||||
import java.nio.charset.StandardCharsets;
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
import java.time.YearMonth;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
@ -196,7 +197,7 @@ public class SubConstructionUserServiceImpl extends ServiceImpl<SubConstructionU
 | 
			
		||||
            .eq(BusAttendance::getUserId, id)
 | 
			
		||||
            .between(BusAttendance::getClockDate, start, end)
 | 
			
		||||
            .list();
 | 
			
		||||
        Map<Date, List<BusAttendance>> dateListMap = dateList.stream().collect(Collectors.groupingBy(BusAttendance::getClockDate));
 | 
			
		||||
        Map<LocalDate, List<BusAttendance>> dateListMap = dateList.stream().collect(Collectors.groupingBy(BusAttendance::getClockDate));
 | 
			
		||||
        // 获取所有打卡图片id
 | 
			
		||||
        List<Long> picIds = dateList.stream().map(BusAttendance::getFacePic).filter(Objects::nonNull).map(Long::valueOf).toList();
 | 
			
		||||
        Map<Long, List<SysOssVo>> ossIdUrlMap = ossService.listByIds(picIds)
 | 
			
		||||
@ -224,9 +225,9 @@ public class SubConstructionUserServiceImpl extends ServiceImpl<SubConstructionU
 | 
			
		||||
            String status;
 | 
			
		||||
            for (BusAttendance attendance : attendanceList) {
 | 
			
		||||
                // 获取上下班状态
 | 
			
		||||
                if (BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(attendance.getCommuter())) {
 | 
			
		||||
                if (BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(attendance.getClockType())) {
 | 
			
		||||
                    clockInStatus = attendance.getClockStatus();
 | 
			
		||||
                } else if (BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getCommuter())) {
 | 
			
		||||
                } else if (BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getClockType())) {
 | 
			
		||||
                    clockOutStatus = attendance.getClockStatus();
 | 
			
		||||
                } else {
 | 
			
		||||
                    clockAllDayStatus = attendance.getClockStatus();
 | 
			
		||||
@ -911,7 +912,7 @@ public class SubConstructionUserServiceImpl extends ServiceImpl<SubConstructionU
 | 
			
		||||
                List<BusAttendance> attendanceList = userIdBusAttendanceListMap.get(id);
 | 
			
		||||
                if (CollUtil.isNotEmpty(attendanceList)) {
 | 
			
		||||
                    // 1. 按打卡日期分组
 | 
			
		||||
                    Map<Date, List<BusAttendance>> dailyMap = attendanceList.stream()
 | 
			
		||||
                    Map<LocalDate, List<BusAttendance>> dailyMap = attendanceList.stream()
 | 
			
		||||
                        .collect(Collectors.groupingBy(BusAttendance::getClockDate));
 | 
			
		||||
                    // 2. 对每一天的记录计算状态
 | 
			
		||||
                    for (List<BusAttendance> dailyList : dailyMap.values()) {
 | 
			
		||||
@ -920,9 +921,9 @@ public class SubConstructionUserServiceImpl extends ServiceImpl<SubConstructionU
 | 
			
		||||
                        String clockAllDayStatus = null;
 | 
			
		||||
                        // 获取上下班状态
 | 
			
		||||
                        for (BusAttendance attendance : dailyList) {
 | 
			
		||||
                            if (BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(attendance.getCommuter())) {
 | 
			
		||||
                            if (BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(attendance.getClockType())) {
 | 
			
		||||
                                clockInStatus = attendance.getClockStatus();
 | 
			
		||||
                            } else if (BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getCommuter())) {
 | 
			
		||||
                            } else if (BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getClockType())) {
 | 
			
		||||
                                clockOutStatus = attendance.getClockStatus();
 | 
			
		||||
                            } else {
 | 
			
		||||
                                clockAllDayStatus = attendance.getClockStatus();
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,260 @@
 | 
			
		||||
package org.dromara.job.attendance;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.date.DateUtil;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 | 
			
		||||
import jakarta.annotation.Resource;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.core.exception.ServiceException;
 | 
			
		||||
import org.dromara.common.core.utils.DateUtils;
 | 
			
		||||
import org.dromara.contractor.domain.SubConstructionUser;
 | 
			
		||||
import org.dromara.project.constant.BusProjectConstant;
 | 
			
		||||
import org.dromara.project.domain.BusAttendance;
 | 
			
		||||
import org.dromara.project.domain.BusAttendanceRule;
 | 
			
		||||
import org.dromara.project.domain.BusProject;
 | 
			
		||||
import org.dromara.project.domain.BusUserProjectRelevancy;
 | 
			
		||||
import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum;
 | 
			
		||||
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
 | 
			
		||||
import org.dromara.project.service.IBusAttendanceRuleService;
 | 
			
		||||
import org.dromara.project.service.IBusAttendanceService;
 | 
			
		||||
import org.dromara.project.service.IBusProjectService;
 | 
			
		||||
import org.dromara.project.service.IBusUserProjectRelevancyService;
 | 
			
		||||
import org.springframework.data.redis.core.Cursor;
 | 
			
		||||
import org.springframework.data.redis.core.ScanOptions;
 | 
			
		||||
import org.springframework.data.redis.core.StringRedisTemplate;
 | 
			
		||||
import org.springframework.scheduling.annotation.Scheduled;
 | 
			
		||||
import org.springframework.stereotype.Component;
 | 
			
		||||
 | 
			
		||||
import java.time.DayOfWeek;
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.time.LocalTime;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
@Slf4j
 | 
			
		||||
@Component
 | 
			
		||||
public class AttendanceJob {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private IBusAttendanceRuleService attendanceRuleService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private IBusUserProjectRelevancyService userProjectRelevancyService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private IBusAttendanceService attendanceService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private IBusProjectService projectService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Scheduled(cron = "0 0/10 * * * ?")
 | 
			
		||||
    public void clockInMiss() {
 | 
			
		||||
        log.info("执行定时任务:上班缺卡记录生成");
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            LocalTime now = LocalTime.now();
 | 
			
		||||
            // 计算当前窗口范围(如当前时间±5分钟)
 | 
			
		||||
            LocalTime start = now.minusMinutes(5);
 | 
			
		||||
            LocalTime end = now.plusMinutes(5);
 | 
			
		||||
 | 
			
		||||
            // 查询所有在窗口内的截止时间规则
 | 
			
		||||
            List<BusAttendanceRule> list;
 | 
			
		||||
            if (start.isAfter(end)) {
 | 
			
		||||
                // 跨天情况:查询时间 >= start OR 时间 < end
 | 
			
		||||
                list = attendanceRuleService.list(Wrappers.<BusAttendanceRule>lambdaQuery()
 | 
			
		||||
                    .and(wrapper -> wrapper.ge(BusAttendanceRule::getClockInResultTime, start)
 | 
			
		||||
                        .or()
 | 
			
		||||
                        .lt(BusAttendanceRule::getClockInResultTime, end)));
 | 
			
		||||
            } else {
 | 
			
		||||
                // 非跨天情况:查询时间 >= start AND 时间 < end
 | 
			
		||||
                list = attendanceRuleService.list(Wrappers.<BusAttendanceRule>lambdaQuery()
 | 
			
		||||
                    .ge(BusAttendanceRule::getClockInResultTime, start)
 | 
			
		||||
                    .lt(BusAttendanceRule::getClockInResultTime, end));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //获取当前日期
 | 
			
		||||
            LocalDate date = LocalDate.now();
 | 
			
		||||
            List<BusAttendance> missList = new ArrayList<>();
 | 
			
		||||
            for (BusAttendanceRule rule : list) {
 | 
			
		||||
                LocalTime clockInTime = rule.getClockInTime();
 | 
			
		||||
                LocalTime clockInResultTime = rule.getClockInResultTime();
 | 
			
		||||
 | 
			
		||||
                //计算考勤日期
 | 
			
		||||
                if (start.isAfter(end)) {  // 跨天情况
 | 
			
		||||
                    if (!clockInResultTime.isBefore(start)) { //在前半段 23:55-00:00
 | 
			
		||||
                        date = date.minusDays(1);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (clockInResultTime.isBefore(end)) { //在后半段 00:00-05:00
 | 
			
		||||
                        if (clockInTime.isAfter(clockInResultTime)) {
 | 
			
		||||
                            date = date.minusDays(1);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (clockInTime.isAfter(clockInResultTime)) {
 | 
			
		||||
                        date = date.minusDays(1);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if(checkSkip(rule,date)){
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //查询项目下的关联人员
 | 
			
		||||
                List<BusUserProjectRelevancy> relevancyList = userProjectRelevancyService.list(Wrappers.lambdaQuery(BusUserProjectRelevancy.class)
 | 
			
		||||
                    .eq(BusUserProjectRelevancy::getProjectId, rule.getProjectId()));
 | 
			
		||||
 | 
			
		||||
                //查询当天已打上班卡人员
 | 
			
		||||
                List<BusAttendance> attendanceList = attendanceService.list(Wrappers.lambdaQuery(BusAttendance.class)
 | 
			
		||||
                    .eq(BusAttendance::getClockDate, date)
 | 
			
		||||
                    .eq(BusAttendance::getClockType, BusAttendanceCommuterEnum.CLOCKIN.getValue())
 | 
			
		||||
                );
 | 
			
		||||
                List<Long> attendanceUserIds = attendanceList.stream().map(BusAttendance::getUserId).toList();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                for (BusUserProjectRelevancy relevancy : relevancyList) {
 | 
			
		||||
 | 
			
		||||
                    if (attendanceUserIds.contains(relevancy.getUserId())) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    BusAttendance busAttendance = new BusAttendance();
 | 
			
		||||
                    //todo: 管理人员 项目id是0
 | 
			
		||||
                    busAttendance.setProjectId(relevancy.getProjectId());
 | 
			
		||||
                    busAttendance.setUserId(relevancy.getUserId());
 | 
			
		||||
                    busAttendance.setClockDate(date);
 | 
			
		||||
                    busAttendance.setClockType(BusAttendanceCommuterEnum.CLOCKIN.getValue());
 | 
			
		||||
                    busAttendance.setClockStatus(BusAttendanceClockStatusEnum.UNCLOCK.getValue());
 | 
			
		||||
 | 
			
		||||
                    missList.add(busAttendance);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //新增
 | 
			
		||||
            if (!missList.isEmpty()) {
 | 
			
		||||
                attendanceService.saveBatch(missList);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("执行定时任务:上班缺卡记录生成异常", e);
 | 
			
		||||
        }
 | 
			
		||||
        log.info("执行定时任务:上班缺卡记录生成完成");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Scheduled(cron = "0 0/10 * * * ?")
 | 
			
		||||
    public void clockOutMiss() {
 | 
			
		||||
        log.info("执行定时任务:下班缺卡记录生成");
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            LocalTime now = LocalTime.now();
 | 
			
		||||
            // 计算当前窗口范围(如当前时间±5分钟)
 | 
			
		||||
            LocalTime start = now.minusMinutes(5);
 | 
			
		||||
            LocalTime end = now.plusMinutes(5);
 | 
			
		||||
 | 
			
		||||
            // 查询所有在窗口内的截止时间规则
 | 
			
		||||
            List<BusAttendanceRule> list;
 | 
			
		||||
            if (start.isAfter(end)) {
 | 
			
		||||
                // 跨天情况:查询时间 >= start OR 时间 < end
 | 
			
		||||
                list = attendanceRuleService.list(Wrappers.<BusAttendanceRule>lambdaQuery()
 | 
			
		||||
                    .and(wrapper -> wrapper.ge(BusAttendanceRule::getClockInResultTime, start)
 | 
			
		||||
                        .or()
 | 
			
		||||
                        .lt(BusAttendanceRule::getClockOutResultTime, end)));
 | 
			
		||||
            } else {
 | 
			
		||||
                // 非跨天情况:查询时间 >= start AND 时间 < end
 | 
			
		||||
                list = attendanceRuleService.list(Wrappers.<BusAttendanceRule>lambdaQuery()
 | 
			
		||||
                    .ge(BusAttendanceRule::getClockInResultTime, start)
 | 
			
		||||
                    .lt(BusAttendanceRule::getClockInResultTime, end));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //获取当前日期
 | 
			
		||||
            LocalDate date = LocalDate.now();
 | 
			
		||||
            List<BusAttendance> missList = new ArrayList<>();
 | 
			
		||||
            for (BusAttendanceRule rule : list) {
 | 
			
		||||
 | 
			
		||||
                LocalTime clockOutTime = rule.getClockOutTime();
 | 
			
		||||
                LocalTime clockOutResultTime = rule.getClockOutResultTime();
 | 
			
		||||
 | 
			
		||||
                //计算考勤日期
 | 
			
		||||
                if (start.isAfter(end)) {  // 跨天情况
 | 
			
		||||
                    if (!clockOutResultTime.isBefore(start)) { //在前半段 23:55-00:00
 | 
			
		||||
                        date = date.minusDays(1);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (clockOutResultTime.isBefore(end)) { //在后半段 00:00-05:00
 | 
			
		||||
                        if (clockOutTime.isAfter(clockOutResultTime)) {
 | 
			
		||||
                            date = date.minusDays(1);
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else {
 | 
			
		||||
                    if (clockOutTime.isAfter(clockOutResultTime)) {
 | 
			
		||||
                        date = date.minusDays(1);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (clockOutTime.isBefore(clockOutResultTime) && rule.getIsNext()) {
 | 
			
		||||
                        date = date.minusDays(1);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                if(checkSkip(rule,date)){
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //查询项目下的关联人员
 | 
			
		||||
                List<BusUserProjectRelevancy> relevancyList = userProjectRelevancyService.list(Wrappers.lambdaQuery(BusUserProjectRelevancy.class)
 | 
			
		||||
                    .eq(BusUserProjectRelevancy::getProjectId, rule.getProjectId()));
 | 
			
		||||
 | 
			
		||||
                //查询当天已打下班卡人员
 | 
			
		||||
                List<BusAttendance> attendanceList = attendanceService.list(Wrappers.lambdaQuery(BusAttendance.class)
 | 
			
		||||
                    .eq(BusAttendance::getClockDate, date)
 | 
			
		||||
                    .eq(BusAttendance::getClockType, BusAttendanceCommuterEnum.CLOCKOUT.getValue())
 | 
			
		||||
                );
 | 
			
		||||
                List<Long> attendanceUserIds = attendanceList.stream().map(BusAttendance::getUserId).toList();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                for (BusUserProjectRelevancy relevancy : relevancyList) {
 | 
			
		||||
 | 
			
		||||
                    if (attendanceUserIds.contains(relevancy.getUserId())) {
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    BusAttendance busAttendance = new BusAttendance();
 | 
			
		||||
                    //todo: 管理人员 项目id是0
 | 
			
		||||
                    busAttendance.setProjectId(relevancy.getProjectId());
 | 
			
		||||
                    busAttendance.setUserId(relevancy.getUserId());
 | 
			
		||||
                    busAttendance.setClockDate(date);
 | 
			
		||||
                    busAttendance.setClockType(BusAttendanceCommuterEnum.CLOCKOUT.getValue());
 | 
			
		||||
                    busAttendance.setClockStatus(BusAttendanceClockStatusEnum.UNCLOCK.getValue());
 | 
			
		||||
 | 
			
		||||
                    missList.add(busAttendance);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //新增
 | 
			
		||||
            if (!missList.isEmpty()) {
 | 
			
		||||
                attendanceService.saveBatch(missList);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (Exception e) {
 | 
			
		||||
            log.error("执行定时任务:下班缺卡记录生成异常", e);
 | 
			
		||||
        }
 | 
			
		||||
        log.info("执行定时任务:下班缺卡记录生成完成");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 检查是否跳过
 | 
			
		||||
     */
 | 
			
		||||
    public Boolean checkSkip(BusAttendanceRule rule,LocalDate  date) {
 | 
			
		||||
        //项目异常状态
 | 
			
		||||
        BusProject byId = projectService.getById(rule.getProjectId());
 | 
			
		||||
        if (byId == null || Objects.equals(byId.getStatus(), "1")) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        // 获取星期几(返回DayOfWeek枚举)
 | 
			
		||||
        DayOfWeek dayOfWeek = date.getDayOfWeek();
 | 
			
		||||
        // 方法2:获取数字(1=星期一,7=星期日)
 | 
			
		||||
        int number = dayOfWeek.getValue();
 | 
			
		||||
 | 
			
		||||
        List<String> list = Arrays.asList(rule.getWeekday().split(","));
 | 
			
		||||
        if (!list.contains(String.valueOf(number))) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -125,4 +125,13 @@ public class OutMonthPlanController extends BaseController {
 | 
			
		||||
        return R.ok(outMonthPlanService.isSubmit(id));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取该月份3种类型计划产值
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("out:monthPlan:query")
 | 
			
		||||
    @GetMapping("/monthInfo")
 | 
			
		||||
    public R<List<OutMonthPlanVo>> infoByPlanMonth(@NotNull(message = "项目ID不能为空") Long projectId,
 | 
			
		||||
                                                   @NotNull(message = "计划月份不能为空") String planMonth) {
 | 
			
		||||
        return R.ok(outMonthPlanService.infoByPlanMonth(projectId,planMonth));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
package org.dromara.out.service;
 | 
			
		||||
 | 
			
		||||
import jakarta.validation.constraints.NotNull;
 | 
			
		||||
import org.dromara.out.domain.vo.OutMonthPlanVo;
 | 
			
		||||
import org.dromara.out.domain.bo.OutMonthPlanBo;
 | 
			
		||||
import org.dromara.out.domain.OutMonthPlan;
 | 
			
		||||
@ -7,6 +8,7 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.extension.service.IService;
 | 
			
		||||
import org.springframework.web.bind.annotation.PathVariable;
 | 
			
		||||
 | 
			
		||||
import java.math.BigDecimal;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
@ -89,4 +91,9 @@ public interface IOutMonthPlanService extends IService<OutMonthPlan>{
 | 
			
		||||
     */
 | 
			
		||||
    BigDecimal getDesignValueByProjectId(Long projectId,String month);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据计划月份查询计划
 | 
			
		||||
     */
 | 
			
		||||
    List<OutMonthPlanVo> infoByPlanMonth(Long projectId, String planMonth);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -62,7 +62,9 @@ public class OutConstructionValueServiceImpl extends ServiceImpl<OutConstruction
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public OutConstructionValueVo queryById(Long id) {
 | 
			
		||||
        return baseMapper.selectVoById(id);
 | 
			
		||||
        OutConstructionValueVo outConstructionValueVo = baseMapper.selectVoById(id);
 | 
			
		||||
        getName(outConstructionValueVo);
 | 
			
		||||
        return outConstructionValueVo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -187,29 +189,33 @@ public class OutConstructionValueServiceImpl extends ServiceImpl<OutConstruction
 | 
			
		||||
     */
 | 
			
		||||
    public void supplementaryData(List<OutConstructionValueVo> list) {
 | 
			
		||||
        for (OutConstructionValueVo vo : list) {
 | 
			
		||||
            //查询项目
 | 
			
		||||
            BusProjectVo busProjectVo = busProjectService.queryById(vo.getProjectId());
 | 
			
		||||
            vo.setProjectName(busProjectVo.getProjectName());
 | 
			
		||||
 | 
			
		||||
            //查询方阵以及子项目
 | 
			
		||||
            FacMatrixVo facMatrixVo = facMatrixService.queryById(vo.getMatrixId());
 | 
			
		||||
            vo.setMatrixName(facMatrixVo.getMatrixName());
 | 
			
		||||
 | 
			
		||||
            BusProjectVo busProjectVo1 = busProjectService.queryById(facMatrixVo.getProjectId());
 | 
			
		||||
            vo.setSubProjectId(busProjectVo1.getId());
 | 
			
		||||
            vo.setSubProjectName(busProjectVo1.getProjectName());
 | 
			
		||||
 | 
			
		||||
            //查询分部工程以及分项工程
 | 
			
		||||
            PgsProgressCategoryVo pgsProgressCategoryVo = pgsProgressCategoryService.queryById(vo.getProgressCategoryId());
 | 
			
		||||
            vo.setProgressCategoryName(pgsProgressCategoryVo.getName());
 | 
			
		||||
 | 
			
		||||
            PgsProgressCategoryVo pgsProgressCategoryVo1 = pgsProgressCategoryService.queryById(pgsProgressCategoryVo.getParentId());
 | 
			
		||||
            vo.setCategoryId(pgsProgressCategoryVo1.getId());
 | 
			
		||||
            vo.setCategoryName(pgsProgressCategoryVo1.getName());
 | 
			
		||||
            getName(vo);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void getName(OutConstructionValueVo  vo){
 | 
			
		||||
        //查询项目
 | 
			
		||||
        BusProjectVo busProjectVo = busProjectService.queryById(vo.getProjectId());
 | 
			
		||||
        vo.setProjectName(busProjectVo.getProjectName());
 | 
			
		||||
 | 
			
		||||
        //查询方阵以及子项目
 | 
			
		||||
        FacMatrixVo facMatrixVo = facMatrixService.queryById(vo.getMatrixId());
 | 
			
		||||
        vo.setMatrixName(facMatrixVo.getMatrixName());
 | 
			
		||||
 | 
			
		||||
        BusProjectVo busProjectVo1 = busProjectService.queryById(facMatrixVo.getProjectId());
 | 
			
		||||
        vo.setSubProjectId(busProjectVo1.getId());
 | 
			
		||||
        vo.setSubProjectName(busProjectVo1.getProjectName());
 | 
			
		||||
 | 
			
		||||
        //查询分部工程以及分项工程
 | 
			
		||||
        PgsProgressCategoryVo pgsProgressCategoryVo = pgsProgressCategoryService.queryById(vo.getProgressCategoryId());
 | 
			
		||||
        vo.setProgressCategoryName(pgsProgressCategoryVo.getName());
 | 
			
		||||
 | 
			
		||||
        PgsProgressCategoryVo pgsProgressCategoryVo1 = pgsProgressCategoryService.queryById(pgsProgressCategoryVo.getParentId());
 | 
			
		||||
        vo.setCategoryId(pgsProgressCategoryVo1.getId());
 | 
			
		||||
        vo.setCategoryName(pgsProgressCategoryVo1.getName());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等)
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ package org.dromara.out.service.impl;
 | 
			
		||||
import cn.hutool.core.convert.Convert;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.cailiaoshebei.service.IBusMaterialsorderService;
 | 
			
		||||
import org.dromara.common.core.domain.event.ProcessDeleteEvent;
 | 
			
		||||
import org.dromara.common.core.domain.event.ProcessEvent;
 | 
			
		||||
import org.dromara.common.core.domain.event.ProcessTaskEvent;
 | 
			
		||||
@ -56,6 +57,9 @@ public class OutMonthPlanServiceImpl extends ServiceImpl<OutMonthPlanMapper, Out
 | 
			
		||||
    private final IOutMonthPlanAuditService outMonthPlanAuditService;
 | 
			
		||||
 | 
			
		||||
    private final IOutConstructionValueService constructionValueService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private final IBusMaterialsorderService busMaterialsorderService;
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询月度产值计划
 | 
			
		||||
     *
 | 
			
		||||
@ -200,6 +204,14 @@ public class OutMonthPlanServiceImpl extends ServiceImpl<OutMonthPlanMapper, Out
 | 
			
		||||
        return designValueByProjectId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<OutMonthPlanVo> infoByPlanMonth(Long projectId, String planMonth) {
 | 
			
		||||
        return baseMapper.selectVoList(Wrappers.<OutMonthPlan>lambdaQuery()
 | 
			
		||||
            .eq(OutMonthPlan::getProjectId, projectId)
 | 
			
		||||
            .eq(OutMonthPlan::getPlanMonth, planMonth)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 总体流程监听(例如: 草稿,撤销,退回,作废,终止,已完成,单任务完成等)
 | 
			
		||||
     * 正常使用只需#processEvent.flowCode=='leave1'
 | 
			
		||||
@ -217,11 +229,13 @@ public class OutMonthPlanServiceImpl extends ServiceImpl<OutMonthPlanMapper, Out
 | 
			
		||||
            .eq(OutMonthPlan::getProjectId, split[0])
 | 
			
		||||
            .eq(OutMonthPlan::getPlanMonth, split[1])
 | 
			
		||||
        );
 | 
			
		||||
        log.info("月度计划产值审核任务,计划数量{}", outMonthPlans.size());
 | 
			
		||||
        outMonthPlans.forEach(outMonthPlan -> {
 | 
			
		||||
            outMonthPlan.setPlanAuditStatus(processEvent.getStatus());
 | 
			
		||||
            if (processEvent.getSubmit()) {
 | 
			
		||||
                outMonthPlan.setPlanAuditStatus(BusinessStatusEnum.WAITING.getStatus());
 | 
			
		||||
            }
 | 
			
		||||
            log.info("月度计划产值审核任务状态改变后{}", outMonthPlan.toString());
 | 
			
		||||
        });
 | 
			
		||||
        if(BusinessStatusEnum.FINISH.getStatus().equals(processEvent.getStatus())){
 | 
			
		||||
            OutMonthPlanAudit outMonthPlanAudit = getOutMonthPlanAudit(outMonthPlans);
 | 
			
		||||
@ -267,8 +281,8 @@ public class OutMonthPlanServiceImpl extends ServiceImpl<OutMonthPlanMapper, Out
 | 
			
		||||
 | 
			
		||||
            if("2".equals(outMonthPlan.getValueType())){ //采购产值
 | 
			
		||||
                //todo: 罗成没写完
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                BigDecimal bigDecimal = busMaterialsorderService.grossOutput(outMonthPlan.getProjectId(), outMonthPlan.getPlanMonth(), 0);
 | 
			
		||||
                outMonthPlan.setCompleteValue(bigDecimal);
 | 
			
		||||
            }else if("3".equals(outMonthPlan.getValueType())){ //施工产值
 | 
			
		||||
                //查询项目的审核通过的施工详细表 1.累计完成产值 2.完成产值月合计 3.各周完成产值
 | 
			
		||||
                List<OutConstructionValue> outConstructionValues = constructionValueService.lambdaQuery()
 | 
			
		||||
 | 
			
		||||
@ -1,33 +1,32 @@
 | 
			
		||||
package org.dromara.project.controller;
 | 
			
		||||
 | 
			
		||||
import cn.dev33.satoken.annotation.SaCheckPermission;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import jakarta.validation.constraints.NotNull;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.excel.utils.ExcelUtil;
 | 
			
		||||
import org.dromara.common.log.annotation.Log;
 | 
			
		||||
import org.dromara.common.log.enums.BusinessType;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.common.web.core.BaseController;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceMonthByUserIdReq;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryReq;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryTwoWeekReq;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceClockDateForTwoWeekVo;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceMonthByUserIdVo;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceVo;
 | 
			
		||||
import org.dromara.project.service.IBusAttendanceService;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import jakarta.validation.constraints.*;
 | 
			
		||||
import cn.dev33.satoken.annotation.SaCheckPermission;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
 | 
			
		||||
import org.dromara.common.log.annotation.Log;
 | 
			
		||||
import org.dromara.common.web.core.BaseController;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.core.validate.AddGroup;
 | 
			
		||||
import org.dromara.common.core.validate.EditGroup;
 | 
			
		||||
import org.dromara.common.log.enums.BusinessType;
 | 
			
		||||
import org.dromara.common.excel.utils.ExcelUtil;
 | 
			
		||||
import org.dromara.project.domain.vo.BusAttendanceVo;
 | 
			
		||||
import org.dromara.project.domain.bo.BusAttendanceBo;
 | 
			
		||||
import org.dromara.project.service.IBusAttendanceService;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤
 | 
			
		||||
 *
 | 
			
		||||
 * @author lilemy
 | 
			
		||||
 * @date 2025-04-07
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
@Validated
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@ -42,26 +41,8 @@ public class BusAttendanceController extends BaseController {
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendance:list")
 | 
			
		||||
    @GetMapping("/list")
 | 
			
		||||
    public TableDataInfo<BusAttendanceVo> list(BusAttendanceQueryReq req, PageQuery pageQuery) {
 | 
			
		||||
        return busAttendanceService.queryPageList(req, pageQuery);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询近两周考勤列表
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendance:list")
 | 
			
		||||
    @GetMapping("/list/clockDate/twoWeek")
 | 
			
		||||
    public R<List<BusAttendanceClockDateForTwoWeekVo>> listClockDateForTwoWeek(BusAttendanceQueryTwoWeekReq req) {
 | 
			
		||||
        return R.ok(busAttendanceService.listClockDateForTwoWeek(req));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询施工人员月份考勤列表
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendance:list")
 | 
			
		||||
    @GetMapping("/list/month/byUserId")
 | 
			
		||||
    public R<List<BusAttendanceMonthByUserIdVo>> listAttendanceMonthListByUserId(BusAttendanceMonthByUserIdReq req) {
 | 
			
		||||
        return R.ok(busAttendanceService.listAttendanceMonthListByUserId(req));
 | 
			
		||||
    public TableDataInfo<BusAttendanceVo> list(BusAttendanceBo bo, PageQuery pageQuery) {
 | 
			
		||||
        return busAttendanceService.queryPageList(bo, pageQuery);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -70,8 +51,8 @@ public class BusAttendanceController extends BaseController {
 | 
			
		||||
    @SaCheckPermission("project:attendance:export")
 | 
			
		||||
    @Log(title = "考勤", businessType = BusinessType.EXPORT)
 | 
			
		||||
    @PostMapping("/export")
 | 
			
		||||
    public void export(BusAttendanceQueryReq req, HttpServletResponse response) {
 | 
			
		||||
        List<BusAttendanceVo> list = busAttendanceService.queryList(req);
 | 
			
		||||
    public void export(BusAttendanceBo bo, HttpServletResponse response) {
 | 
			
		||||
        List<BusAttendanceVo> list = busAttendanceService.queryList(bo);
 | 
			
		||||
        ExcelUtil.exportExcel(list, "考勤", BusAttendanceVo.class, response);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -83,8 +64,42 @@ public class BusAttendanceController extends BaseController {
 | 
			
		||||
    @SaCheckPermission("project:attendance:query")
 | 
			
		||||
    @GetMapping("/{id}")
 | 
			
		||||
    public R<BusAttendanceVo> getInfo(@NotNull(message = "主键不能为空")
 | 
			
		||||
                                      @PathVariable Long id) {
 | 
			
		||||
                                     @PathVariable Long id) {
 | 
			
		||||
        return R.ok(busAttendanceService.queryById(id));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 新增考勤
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendance:add")
 | 
			
		||||
    @Log(title = "考勤", businessType = BusinessType.INSERT)
 | 
			
		||||
    @RepeatSubmit()
 | 
			
		||||
    @PostMapping()
 | 
			
		||||
    public R<Void> add(@Validated(AddGroup.class) @RequestBody BusAttendanceBo bo) {
 | 
			
		||||
        return toAjax(busAttendanceService.insertByBo(bo));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 修改考勤
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendance:edit")
 | 
			
		||||
    @Log(title = "考勤", businessType = BusinessType.UPDATE)
 | 
			
		||||
    @RepeatSubmit()
 | 
			
		||||
    @PutMapping()
 | 
			
		||||
    public R<Void> edit(@Validated(EditGroup.class) @RequestBody BusAttendanceBo bo) {
 | 
			
		||||
        return toAjax(busAttendanceService.updateByBo(bo));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 删除考勤
 | 
			
		||||
     *
 | 
			
		||||
     * @param ids 主键串
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendance:remove")
 | 
			
		||||
    @Log(title = "考勤", businessType = BusinessType.DELETE)
 | 
			
		||||
    @DeleteMapping("/{ids}")
 | 
			
		||||
    public R<Void> remove(@NotEmpty(message = "主键不能为空")
 | 
			
		||||
                          @PathVariable Long[] ids) {
 | 
			
		||||
        return toAjax(busAttendanceService.deleteWithValidByIds(List.of(ids), true));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,105 @@
 | 
			
		||||
package org.dromara.project.controller;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import jakarta.servlet.http.HttpServletResponse;
 | 
			
		||||
import jakarta.validation.constraints.*;
 | 
			
		||||
import cn.dev33.satoken.annotation.SaCheckPermission;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.dromara.common.idempotent.annotation.RepeatSubmit;
 | 
			
		||||
import org.dromara.common.log.annotation.Log;
 | 
			
		||||
import org.dromara.common.web.core.BaseController;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.core.validate.AddGroup;
 | 
			
		||||
import org.dromara.common.core.validate.EditGroup;
 | 
			
		||||
import org.dromara.common.log.enums.BusinessType;
 | 
			
		||||
import org.dromara.common.excel.utils.ExcelUtil;
 | 
			
		||||
import org.dromara.project.domain.vo.BusAttendanceRuleVo;
 | 
			
		||||
import org.dromara.project.domain.bo.BusAttendanceRuleBo;
 | 
			
		||||
import org.dromara.project.service.IBusAttendanceRuleService;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤打卡规则
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
@Validated
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/project/attendanceRule")
 | 
			
		||||
public class BusAttendanceRuleController extends BaseController {
 | 
			
		||||
 | 
			
		||||
    private final IBusAttendanceRuleService busAttendanceRuleService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询考勤打卡规则列表
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendanceRule:list")
 | 
			
		||||
    @GetMapping("/list")
 | 
			
		||||
    public TableDataInfo<BusAttendanceRuleVo> list(BusAttendanceRuleBo bo, PageQuery pageQuery) {
 | 
			
		||||
        return busAttendanceRuleService.queryPageList(bo, pageQuery);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 导出考勤打卡规则列表
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendanceRule:export")
 | 
			
		||||
    @Log(title = "考勤打卡规则", businessType = BusinessType.EXPORT)
 | 
			
		||||
    @PostMapping("/export")
 | 
			
		||||
    public void export(BusAttendanceRuleBo bo, HttpServletResponse response) {
 | 
			
		||||
        List<BusAttendanceRuleVo> list = busAttendanceRuleService.queryList(bo);
 | 
			
		||||
        ExcelUtil.exportExcel(list, "考勤打卡规则", BusAttendanceRuleVo.class, response);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取考勤打卡规则详细信息
 | 
			
		||||
     *
 | 
			
		||||
     * @param id 主键
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendanceRule:query")
 | 
			
		||||
    @GetMapping("/{id}")
 | 
			
		||||
    public R<BusAttendanceRuleVo> getInfo(@NotNull(message = "主键不能为空")
 | 
			
		||||
                                     @PathVariable Long id) {
 | 
			
		||||
        return R.ok(busAttendanceRuleService.queryById(id));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 新增考勤打卡规则
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendanceRule:add")
 | 
			
		||||
    @Log(title = "考勤打卡规则", businessType = BusinessType.INSERT)
 | 
			
		||||
    @RepeatSubmit()
 | 
			
		||||
    @PostMapping()
 | 
			
		||||
    public R<Void> add(@Validated(AddGroup.class) @RequestBody BusAttendanceRuleBo bo) {
 | 
			
		||||
        return toAjax(busAttendanceRuleService.insertByBo(bo));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 修改考勤打卡规则
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendanceRule:edit")
 | 
			
		||||
    @Log(title = "考勤打卡规则", businessType = BusinessType.UPDATE)
 | 
			
		||||
    @RepeatSubmit()
 | 
			
		||||
    @PutMapping()
 | 
			
		||||
    public R<Void> edit(@Validated(EditGroup.class) @RequestBody BusAttendanceRuleBo bo) {
 | 
			
		||||
        return toAjax(busAttendanceRuleService.updateByBo(bo));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 删除考勤打卡规则
 | 
			
		||||
     *
 | 
			
		||||
     * @param ids 主键串
 | 
			
		||||
     */
 | 
			
		||||
    @SaCheckPermission("project:attendanceRule:remove")
 | 
			
		||||
    @Log(title = "考勤打卡规则", businessType = BusinessType.DELETE)
 | 
			
		||||
    @DeleteMapping("/{ids}")
 | 
			
		||||
    public R<Void> remove(@NotEmpty(message = "主键不能为空")
 | 
			
		||||
                          @PathVariable Long[] ids) {
 | 
			
		||||
        return toAjax(busAttendanceRuleService.deleteWithValidByIds(List.of(ids), true));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,16 +1,14 @@
 | 
			
		||||
package org.dromara.project.controller.app;
 | 
			
		||||
 | 
			
		||||
import jakarta.annotation.Resource;
 | 
			
		||||
import jakarta.validation.constraints.NotNull;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.satoken.utils.LoginHelper;
 | 
			
		||||
import org.dromara.contractor.domain.SubConstructionUser;
 | 
			
		||||
import org.dromara.contractor.service.ISubConstructionUserService;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceByDayReq;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceByMonthReq;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceMonthByUserIdReq;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceMonthByUserIdVo;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceVo;
 | 
			
		||||
import org.dromara.project.domain.vo.BusAttendanceRuleVo;
 | 
			
		||||
import org.dromara.project.domain.vo.BusAttendanceVo;
 | 
			
		||||
import org.dromara.project.service.IBusAttendanceRuleService;
 | 
			
		||||
import org.dromara.project.service.IBusAttendanceService;
 | 
			
		||||
import org.springframework.validation.annotation.Validated;
 | 
			
		||||
import org.springframework.web.bind.annotation.*;
 | 
			
		||||
@ -26,38 +24,15 @@ import java.util.List;
 | 
			
		||||
 */
 | 
			
		||||
@Validated
 | 
			
		||||
@RestController
 | 
			
		||||
@RequestMapping("/app/project/attendance")
 | 
			
		||||
@RequestMapping("/app/project/attendance/app")
 | 
			
		||||
public class BusAttendanceAppController {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private IBusAttendanceService attendanceService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private ISubConstructionUserService constructionUserService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取当前登录用户的考勤列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param req 查询参数
 | 
			
		||||
     * @return 考勤列表
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("/list/loginUser")
 | 
			
		||||
    public R<List<BusAttendanceVo>> listLoginUser(BusAttendanceByDayReq req) {
 | 
			
		||||
        return R.ok(attendanceService.getDayByUserId(LoginHelper.getUserId(), req.getClockDate()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取当前登录用户月份考勤列表
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("/list/month/loginUser")
 | 
			
		||||
    public R<List<BusAttendanceMonthByUserIdVo>> listAttendanceMonthListByUserId(BusAttendanceByMonthReq req) {
 | 
			
		||||
        Long userId = LoginHelper.getUserId();
 | 
			
		||||
        SubConstructionUser constructionUser = constructionUserService.getBySysUserId(userId);
 | 
			
		||||
        BusAttendanceMonthByUserIdReq dto = new BusAttendanceMonthByUserIdReq();
 | 
			
		||||
        dto.setUserId(constructionUser.getId());
 | 
			
		||||
        dto.setClockMonth(req.getClockMonth());
 | 
			
		||||
        return R.ok(attendanceService.listAttendanceMonthListByUserId(dto));
 | 
			
		||||
    }
 | 
			
		||||
    private  IBusAttendanceRuleService attendanceRuleService;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 人脸坐标打卡
 | 
			
		||||
@ -66,4 +41,44 @@ public class BusAttendanceAppController {
 | 
			
		||||
    public R<Boolean> punchCardByFace(@RequestPart("file") MultipartFile file, BusAttendancePunchCardByFaceReq req) {
 | 
			
		||||
        return R.ok(attendanceService.punchCardByFace(file, req));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取考勤打卡规则详细信息
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("/ruleInfo/{projectId}")
 | 
			
		||||
    public R<BusAttendanceRuleVo> getInfo(@NotNull(message = "项目不能为空")
 | 
			
		||||
                                          @PathVariable Long projectId) {
 | 
			
		||||
        return R.ok(attendanceRuleService.queryByProjectId(projectId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询项目考勤范围列表
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("/punchRangeList/{projectId}")
 | 
			
		||||
    public R<List<String>> list(@NotNull @PathVariable("projectId") Long projectId) {
 | 
			
		||||
        Long userId = LoginHelper.getUserId();
 | 
			
		||||
        return R.ok(attendanceService.getPunchRangeByProjectIdAndUserId(projectId, userId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 判断是否在打卡范围内
 | 
			
		||||
     */
 | 
			
		||||
    @GetMapping("/checkInRange")
 | 
			
		||||
    public R<Boolean> checkInRange(BusAttendancePunchCardByFaceReq req) {
 | 
			
		||||
        return R.ok(attendanceService.checkInRange(req));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取用户当天打卡记录
 | 
			
		||||
     */
 | 
			
		||||
    @PostMapping("/getTodayAttendance/{projectId}")
 | 
			
		||||
    public R<List<BusAttendanceVo>> getTodayAttendance(@NotNull @PathVariable("projectId") Long projectId){
 | 
			
		||||
        return R.ok(attendanceService.getTodayAttendance(projectId));
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,19 +1,22 @@
 | 
			
		||||
package org.dromara.project.domain;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.TableId;
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.TableName;
 | 
			
		||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.*;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonFormat;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤对象 bus_attendance
 | 
			
		||||
 *
 | 
			
		||||
 * @author lilemy
 | 
			
		||||
 * @date 2025-04-07
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@ -24,7 +27,7 @@ public class BusAttendance extends BaseEntity {
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 主键id
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    @TableId(value = "id")
 | 
			
		||||
    private Long id;
 | 
			
		||||
@ -34,11 +37,6 @@ public class BusAttendance extends BaseEntity {
 | 
			
		||||
     */
 | 
			
		||||
    private Long userId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 人员姓名
 | 
			
		||||
     */
 | 
			
		||||
    private String userName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 人脸照
 | 
			
		||||
     */
 | 
			
		||||
@ -49,50 +47,30 @@ public class BusAttendance extends BaseEntity {
 | 
			
		||||
     */
 | 
			
		||||
    private Long projectId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private Date clockTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡日期
 | 
			
		||||
     */
 | 
			
		||||
    private Date clockDate;
 | 
			
		||||
    private LocalDate clockDate;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡状态(1正常,2迟到,3早退,4缺勤,5补卡)
 | 
			
		||||
     * 打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime clockTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 1正常,2迟到,3早退,4缺勤,5补卡,6请假,7外勤
 | 
			
		||||
     */
 | 
			
		||||
    private String clockStatus;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 请假id
 | 
			
		||||
     * 上下班(1上班2下班)
 | 
			
		||||
     */
 | 
			
		||||
    private Long leaveId;
 | 
			
		||||
    private String clockType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 代打人员id
 | 
			
		||||
     * 打卡地点
 | 
			
		||||
     */
 | 
			
		||||
    private Long pinchUserId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 多次打卡时间记录
 | 
			
		||||
     */
 | 
			
		||||
    private String clockRecord;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上下班(1上班,2下班)
 | 
			
		||||
     */
 | 
			
		||||
    private String commuter;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡范围
 | 
			
		||||
     */
 | 
			
		||||
    private String punchRange;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 日薪
 | 
			
		||||
     */
 | 
			
		||||
    private Long dailyWage;
 | 
			
		||||
    private String clockLocation;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 经度
 | 
			
		||||
@ -104,9 +82,5 @@ public class BusAttendance extends BaseEntity {
 | 
			
		||||
     */
 | 
			
		||||
    private String lat;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 备注
 | 
			
		||||
     */
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,96 @@
 | 
			
		||||
package org.dromara.project.domain;
 | 
			
		||||
 | 
			
		||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
 | 
			
		||||
import com.baomidou.mybatisplus.annotation.*;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.time.LocalTime;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonFormat;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤打卡规则对象 bus_attendance_rule
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@TableName("bus_attendance_rule")
 | 
			
		||||
public class BusAttendanceRule extends BaseEntity {
 | 
			
		||||
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    @TableId(value = "id")
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 项目id
 | 
			
		||||
     */
 | 
			
		||||
    private Long projectId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockInTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockOutTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否次日
 | 
			
		||||
     */
 | 
			
		||||
    private Boolean isNext;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班开始打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockInStartTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班结束打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockInEndTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班开始打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockOutStartTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班结束打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockOutEndTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班结果生成时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockInResultTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班结果生成时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockOutResultTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 工作日
 | 
			
		||||
     */
 | 
			
		||||
    private String weekday;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 1-无限制,2-范围内打卡
 | 
			
		||||
     */
 | 
			
		||||
    private String type;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,87 @@
 | 
			
		||||
package org.dromara.project.domain.bo;
 | 
			
		||||
 | 
			
		||||
import org.dromara.project.domain.BusAttendance;
 | 
			
		||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
 | 
			
		||||
import org.dromara.common.core.validate.AddGroup;
 | 
			
		||||
import org.dromara.common.core.validate.EditGroup;
 | 
			
		||||
import io.github.linpeilie.annotations.AutoMapper;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import jakarta.validation.constraints.*;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonFormat;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤业务对象 bus_attendance
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@AutoMapper(target = BusAttendance.class, reverseConvertGenerate = false)
 | 
			
		||||
public class BusAttendanceBo extends BaseEntity {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "不能为空", groups = { EditGroup.class })
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 人员id
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "人员id不能为空", groups = { AddGroup.class, EditGroup.class })
 | 
			
		||||
    private Long userId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 人脸照
 | 
			
		||||
     */
 | 
			
		||||
    private String facePic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 项目id
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "项目id不能为空", groups = { AddGroup.class, EditGroup.class })
 | 
			
		||||
    private Long projectId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡日期
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDate clockDate;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDateTime clockTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 1正常,2迟到,3早退,4缺勤,5补卡,6请假,7外勤
 | 
			
		||||
     */
 | 
			
		||||
    private String clockStatus;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上下班(1上班2下班)
 | 
			
		||||
     */
 | 
			
		||||
    private String clockType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡地点
 | 
			
		||||
     */
 | 
			
		||||
    private String clockLocation;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 经度
 | 
			
		||||
     */
 | 
			
		||||
    private String lng;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 纬度
 | 
			
		||||
     */
 | 
			
		||||
    private String lat;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,98 @@
 | 
			
		||||
package org.dromara.project.domain.bo;
 | 
			
		||||
 | 
			
		||||
import org.dromara.project.domain.BusAttendanceRule;
 | 
			
		||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
 | 
			
		||||
import org.dromara.common.core.validate.AddGroup;
 | 
			
		||||
import org.dromara.common.core.validate.EditGroup;
 | 
			
		||||
import io.github.linpeilie.annotations.AutoMapper;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import lombok.EqualsAndHashCode;
 | 
			
		||||
import jakarta.validation.constraints.*;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.time.LocalTime;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonFormat;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤打卡规则业务对象 bus_attendance_rule
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@EqualsAndHashCode(callSuper = true)
 | 
			
		||||
@AutoMapper(target = BusAttendanceRule.class, reverseConvertGenerate = false)
 | 
			
		||||
public class BusAttendanceRuleBo extends BaseEntity {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "不能为空", groups = { EditGroup.class })
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 项目id
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "项目id不能为空", groups = { AddGroup.class, EditGroup.class })
 | 
			
		||||
    private Long projectId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "上班打卡时间不能为空", groups = { AddGroup.class, EditGroup.class })
 | 
			
		||||
    private LocalTime clockInTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "下班打卡时间不能为空", groups = { AddGroup.class, EditGroup.class })
 | 
			
		||||
    private LocalTime clockOutTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否次日
 | 
			
		||||
     */
 | 
			
		||||
    private Boolean isNext;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班开始打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockInStartTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班结束打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockInEndTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班开始打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockOutStartTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班结束打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockOutEndTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班结果生成时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockInResultTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班结果生成时间
 | 
			
		||||
     */
 | 
			
		||||
    private LocalTime clockOutResultTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 工作日
 | 
			
		||||
     */
 | 
			
		||||
    private String weekday;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 1-无限制,2-范围内打卡
 | 
			
		||||
     */
 | 
			
		||||
    private String type;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
package org.dromara.project.domain.bo;
 | 
			
		||||
 | 
			
		||||
import lombok.Builder;
 | 
			
		||||
import org.dromara.project.domain.BusProjectPunchrange;
 | 
			
		||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
 | 
			
		||||
import org.dromara.common.core.validate.AddGroup;
 | 
			
		||||
@ -21,7 +22,7 @@ import jakarta.validation.constraints.*;
 | 
			
		||||
public class BusProjectPunchrangeBo extends BaseEntity {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    @NotNull(message = "不能为空", groups = { EditGroup.class })
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
package org.dromara.project.domain.dto.attendance;
 | 
			
		||||
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
@ -25,4 +26,14 @@ public class BusAttendancePunchCardByFaceReq implements Serializable {
 | 
			
		||||
     */
 | 
			
		||||
    private String lat;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 地名
 | 
			
		||||
     */
 | 
			
		||||
    private String locationName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 项目Id
 | 
			
		||||
     */
 | 
			
		||||
    private Long projectId;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,113 @@
 | 
			
		||||
package org.dromara.project.domain.vo;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalTime;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonFormat;
 | 
			
		||||
import org.dromara.project.domain.BusAttendanceRule;
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelProperty;
 | 
			
		||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
 | 
			
		||||
import org.dromara.common.excel.convert.ExcelDictConvert;
 | 
			
		||||
import io.github.linpeilie.annotations.AutoMapper;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤打卡规则视图对象 bus_attendance_rule
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@ExcelIgnoreUnannotated
 | 
			
		||||
@AutoMapper(target = BusAttendanceRule.class)
 | 
			
		||||
public class BusAttendanceRuleVo implements Serializable {
 | 
			
		||||
 | 
			
		||||
    @Serial
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "")
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 项目id
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "项目id")
 | 
			
		||||
    private Long projectId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "上班打卡时间")
 | 
			
		||||
    private LocalTime clockInTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "下班打卡时间")
 | 
			
		||||
    private LocalTime clockOutTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 是否次日
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "是否次日")
 | 
			
		||||
    private Boolean isNext;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班开始打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "上班开始打卡时间")
 | 
			
		||||
    private LocalTime clockInStartTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班结束打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "上班结束打卡时间")
 | 
			
		||||
    private LocalTime clockInEndTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班开始打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "下班开始打卡时间")
 | 
			
		||||
    private LocalTime clockOutStartTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班结束打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "下班结束打卡时间")
 | 
			
		||||
    private LocalTime clockOutEndTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上班结果生成时间
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "上班结果生成时间")
 | 
			
		||||
    private LocalTime clockInResultTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 下班结果生成时间
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "下班结果生成时间")
 | 
			
		||||
    private LocalTime clockOutResultTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 工作日
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "工作日")
 | 
			
		||||
    private String weekday;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 1-无限制,2-范围内打卡
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "1-无限制,2-范围内打卡")
 | 
			
		||||
    private String type;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,24 +1,28 @@
 | 
			
		||||
package org.dromara.project.domain.vo.attendance;
 | 
			
		||||
package org.dromara.project.domain.vo;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonFormat;
 | 
			
		||||
import org.dromara.project.domain.BusAttendance;
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
 | 
			
		||||
import com.alibaba.excel.annotation.ExcelProperty;
 | 
			
		||||
import com.fasterxml.jackson.annotation.JsonFormat;
 | 
			
		||||
import io.github.linpeilie.annotations.AutoMapper;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
 | 
			
		||||
import org.dromara.common.excel.convert.ExcelDictConvert;
 | 
			
		||||
import org.dromara.project.domain.BusAttendance;
 | 
			
		||||
import io.github.linpeilie.annotations.AutoMapper;
 | 
			
		||||
import lombok.Data;
 | 
			
		||||
 | 
			
		||||
import java.io.Serial;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤视图对象 bus_attendance
 | 
			
		||||
 *
 | 
			
		||||
 * @author lilemy
 | 
			
		||||
 * @date 2025-04-07
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
@Data
 | 
			
		||||
@ExcelIgnoreUnannotated
 | 
			
		||||
@ -29,36 +33,21 @@ public class BusAttendanceVo implements Serializable {
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 主键id
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "")
 | 
			
		||||
    private Long id;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 人员id
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "人员id")
 | 
			
		||||
    private Long userId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 人员姓名
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "人员姓名")
 | 
			
		||||
    private String userName;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 班组id
 | 
			
		||||
     */
 | 
			
		||||
    private Long teamId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 工种
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "工种", converter = ExcelDictConvert.class)
 | 
			
		||||
    @ExcelDictFormat(dictType = "type_of_work")
 | 
			
		||||
    private String typeOfWork;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 人脸照
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "人脸照")
 | 
			
		||||
    private String facePic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -67,57 +56,36 @@ public class BusAttendanceVo implements Serializable {
 | 
			
		||||
    @ExcelProperty(value = "项目id")
 | 
			
		||||
    private Long projectId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡日期
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "打卡日期")
 | 
			
		||||
    private LocalDate clockDate;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "打卡时间")
 | 
			
		||||
    private Date clockTime;
 | 
			
		||||
    private LocalDateTime clockTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡日期
 | 
			
		||||
     * 1正常,2迟到,3早退,4缺勤,5补卡,6请假,7外勤
 | 
			
		||||
     */
 | 
			
		||||
    @JsonFormat(shape = JsonFormat.Shape.STRING,
 | 
			
		||||
        pattern = "yyyy-MM-dd",
 | 
			
		||||
        timezone = "GMT+8")
 | 
			
		||||
    @ExcelProperty(value = "打卡日期")
 | 
			
		||||
    private Date clockDate;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡状态(1正常,2迟到,3早退,4缺勤,5补卡)
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "打卡状态", converter = ExcelDictConvert.class)
 | 
			
		||||
    @ExcelDictFormat(dictType = "clock_status_type")
 | 
			
		||||
    @ExcelProperty(value = "1正常,2迟到,3早退,4缺勤,5补卡,6请假,7外勤")
 | 
			
		||||
    private String clockStatus;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 代打人员id
 | 
			
		||||
     */
 | 
			
		||||
    private Long pinchUserId;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 多次打卡时间记录
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "多次打卡时间记录")
 | 
			
		||||
    private String clockRecord;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 上下班(1上班,2下班)
 | 
			
		||||
     * 上下班(1上班2下班)
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "上下班", converter = ExcelDictConvert.class)
 | 
			
		||||
    @ExcelDictFormat(dictType = "commuter_type")
 | 
			
		||||
    private String commuter;
 | 
			
		||||
    @ExcelDictFormat(readConverterExp = "1=上班2下班")
 | 
			
		||||
    private String clockType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡范围
 | 
			
		||||
     * 打卡地点
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "打卡范围")
 | 
			
		||||
    private String punchRange;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 日薪
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "日薪")
 | 
			
		||||
    private Long dailyWage;
 | 
			
		||||
    @ExcelProperty(value = "打卡地点")
 | 
			
		||||
    private String clockLocation;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 经度
 | 
			
		||||
@ -131,10 +99,5 @@ public class BusAttendanceVo implements Serializable {
 | 
			
		||||
    @ExcelProperty(value = "纬度")
 | 
			
		||||
    private String lat;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 备注
 | 
			
		||||
     */
 | 
			
		||||
    @ExcelProperty(value = "备注")
 | 
			
		||||
    private String remark;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -5,6 +5,8 @@ import lombok.Data;
 | 
			
		||||
import lombok.NoArgsConstructor;
 | 
			
		||||
import org.dromara.project.domain.BusAttendance;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -19,12 +21,12 @@ public class BusAttendanceListByDay {
 | 
			
		||||
    /**
 | 
			
		||||
     * 上下班(1上班,2下班)
 | 
			
		||||
     */
 | 
			
		||||
    private String commuter;
 | 
			
		||||
    private String clockType;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡时间
 | 
			
		||||
     */
 | 
			
		||||
    private Date clockTime;
 | 
			
		||||
    private LocalDateTime clockTime;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 打卡状态(1正常,2迟到,3早退,4缺勤,5补卡)
 | 
			
		||||
@ -36,7 +38,7 @@ public class BusAttendanceListByDay {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        BusAttendanceListByDay attendanceListByDay = new BusAttendanceListByDay();
 | 
			
		||||
        attendanceListByDay.setCommuter(attendance.getCommuter());
 | 
			
		||||
        attendanceListByDay.setClockType(attendance.getClockType());
 | 
			
		||||
        attendanceListByDay.setClockTime(attendance.getClockTime());
 | 
			
		||||
        attendanceListByDay.setClockStatus(attendance.getClockStatus());
 | 
			
		||||
        return attendanceListByDay;
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,14 @@
 | 
			
		||||
package org.dromara.project.mapper;
 | 
			
		||||
 | 
			
		||||
import org.dromara.project.domain.BusAttendance;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceVo;
 | 
			
		||||
import org.dromara.project.domain.vo.BusAttendanceVo;
 | 
			
		||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤Mapper接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author lilemy
 | 
			
		||||
 * @date 2025-04-07
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
public interface BusAttendanceMapper extends BaseMapperPlus<BusAttendance, BusAttendanceVo> {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,15 @@
 | 
			
		||||
package org.dromara.project.mapper;
 | 
			
		||||
 | 
			
		||||
import org.dromara.project.domain.BusAttendanceRule;
 | 
			
		||||
import org.dromara.project.domain.vo.BusAttendanceRuleVo;
 | 
			
		||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤打卡规则Mapper接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
public interface BusAttendanceRuleMapper extends BaseMapperPlus<BusAttendanceRule, BusAttendanceRuleVo> {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,75 @@
 | 
			
		||||
package org.dromara.project.service;
 | 
			
		||||
 | 
			
		||||
import org.dromara.project.domain.vo.BusAttendanceRuleVo;
 | 
			
		||||
import org.dromara.project.domain.bo.BusAttendanceRuleBo;
 | 
			
		||||
import org.dromara.project.domain.BusAttendanceRule;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.extension.service.IService;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤打卡规则Service接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
public interface IBusAttendanceRuleService extends IService<BusAttendanceRule>{
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询考勤打卡规则
 | 
			
		||||
     *
 | 
			
		||||
     * @param id 主键
 | 
			
		||||
     * @return 考勤打卡规则
 | 
			
		||||
     */
 | 
			
		||||
    BusAttendanceRuleVo queryById(Long id);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询考勤打卡规则列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param bo        查询条件
 | 
			
		||||
     * @param pageQuery 分页参数
 | 
			
		||||
     * @return 考勤打卡规则分页列表
 | 
			
		||||
     */
 | 
			
		||||
    TableDataInfo<BusAttendanceRuleVo> queryPageList(BusAttendanceRuleBo bo, PageQuery pageQuery);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询符合条件的考勤打卡规则列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param bo 查询条件
 | 
			
		||||
     * @return 考勤打卡规则列表
 | 
			
		||||
     */
 | 
			
		||||
    List<BusAttendanceRuleVo> queryList(BusAttendanceRuleBo bo);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 新增考勤打卡规则
 | 
			
		||||
     *
 | 
			
		||||
     * @param bo 考勤打卡规则
 | 
			
		||||
     * @return 是否新增成功
 | 
			
		||||
     */
 | 
			
		||||
    Boolean insertByBo(BusAttendanceRuleBo bo);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 修改考勤打卡规则
 | 
			
		||||
     *
 | 
			
		||||
     * @param bo 考勤打卡规则
 | 
			
		||||
     * @return 是否修改成功
 | 
			
		||||
     */
 | 
			
		||||
    Boolean updateByBo(BusAttendanceRuleBo bo);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验并批量删除考勤打卡规则信息
 | 
			
		||||
     *
 | 
			
		||||
     * @param ids     待删除的主键集合
 | 
			
		||||
     * @param isValid 是否进行有效性校验
 | 
			
		||||
     * @return 是否删除成功
 | 
			
		||||
     */
 | 
			
		||||
    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     *  根据项目id查询
 | 
			
		||||
     */
 | 
			
		||||
    BusAttendanceRuleVo queryByProjectId(Long projectId);
 | 
			
		||||
}
 | 
			
		||||
@ -1,30 +1,25 @@
 | 
			
		||||
package org.dromara.project.service;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.service.IService;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.project.domain.BusAttendance;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceMonthByUserIdReq;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryReq;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryTwoWeekReq;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceClockDateForTwoWeekVo;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceMonthByUserIdVo;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceVo;
 | 
			
		||||
import org.dromara.project.domain.vo.BusAttendanceVo;
 | 
			
		||||
import org.dromara.project.domain.bo.BusAttendanceBo;
 | 
			
		||||
import org.dromara.project.domain.BusAttendance;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.extension.service.IService;
 | 
			
		||||
import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
 | 
			
		||||
import java.util.Date;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤Service接口
 | 
			
		||||
 *
 | 
			
		||||
 * @author lilemy
 | 
			
		||||
 * @date 2025-04-07
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
public interface IBusAttendanceService extends IService<BusAttendance> {
 | 
			
		||||
public interface IBusAttendanceService extends IService<BusAttendance>{
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询考勤
 | 
			
		||||
@ -37,76 +32,45 @@ public interface IBusAttendanceService extends IService<BusAttendance> {
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询考勤列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param req       查询条件
 | 
			
		||||
     * @param bo        查询条件
 | 
			
		||||
     * @param pageQuery 分页参数
 | 
			
		||||
     * @return 考勤分页列表
 | 
			
		||||
     */
 | 
			
		||||
    TableDataInfo<BusAttendanceVo> queryPageList(BusAttendanceQueryReq req, PageQuery pageQuery);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询两周内的考勤列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param req 查询条件
 | 
			
		||||
     * @return 考勤列表
 | 
			
		||||
     */
 | 
			
		||||
    List<BusAttendanceClockDateForTwoWeekVo> listClockDateForTwoWeek(BusAttendanceQueryTwoWeekReq req);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询用户每月考勤列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param req 查询条件
 | 
			
		||||
     * @return 考勤列表
 | 
			
		||||
     */
 | 
			
		||||
    List<BusAttendanceMonthByUserIdVo> listAttendanceMonthListByUserId(BusAttendanceMonthByUserIdReq req);
 | 
			
		||||
    TableDataInfo<BusAttendanceVo> queryPageList(BusAttendanceBo bo, PageQuery pageQuery);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询符合条件的考勤列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param req 查询条件
 | 
			
		||||
     * @param bo 查询条件
 | 
			
		||||
     * @return 考勤列表
 | 
			
		||||
     */
 | 
			
		||||
    List<BusAttendanceVo> queryList(BusAttendanceQueryReq req);
 | 
			
		||||
    List<BusAttendanceVo> queryList(BusAttendanceBo bo);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据项目id查询出勤人列表
 | 
			
		||||
     * 新增考勤
 | 
			
		||||
     *
 | 
			
		||||
     * @param projectId 项目id
 | 
			
		||||
     * @return 出勤人列表
 | 
			
		||||
     * @param bo 考勤
 | 
			
		||||
     * @return 是否新增成功
 | 
			
		||||
     */
 | 
			
		||||
    List<Long> listAttendancePeopleByProjectId(Long projectId);
 | 
			
		||||
    Boolean insertByBo(BusAttendanceBo bo);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取考勤视图对象
 | 
			
		||||
     * 修改考勤
 | 
			
		||||
     *
 | 
			
		||||
     * @param attendance 考勤对象
 | 
			
		||||
     * @return 考勤视图对象
 | 
			
		||||
     * @param bo 考勤
 | 
			
		||||
     * @return 是否修改成功
 | 
			
		||||
     */
 | 
			
		||||
    BusAttendanceVo getVo(BusAttendance attendance);
 | 
			
		||||
    Boolean updateByBo(BusAttendanceBo bo);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取考勤查询条件封装
 | 
			
		||||
     * 校验并批量删除考勤信息
 | 
			
		||||
     *
 | 
			
		||||
     * @param req 查询条件
 | 
			
		||||
     * @return 查询条件封装
 | 
			
		||||
     * @param ids     待删除的主键集合
 | 
			
		||||
     * @param isValid 是否进行有效性校验
 | 
			
		||||
     * @return 是否删除成功
 | 
			
		||||
     */
 | 
			
		||||
    LambdaQueryWrapper<BusAttendance> buildQueryWrapper(BusAttendanceQueryReq req);
 | 
			
		||||
    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取考勤分页对象视图
 | 
			
		||||
     *
 | 
			
		||||
     * @param attendancePage 考勤分页对象
 | 
			
		||||
     * @return 考勤分页对象视图
 | 
			
		||||
     */
 | 
			
		||||
    Page<BusAttendanceVo> getVoPage(Page<BusAttendance> attendancePage);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据系统用户id和日期查询考勤
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId    用户id
 | 
			
		||||
     * @param clockDate 日期
 | 
			
		||||
     * @return 考勤
 | 
			
		||||
     */
 | 
			
		||||
    List<BusAttendanceVo> getDayByUserId(Long userId, Date clockDate);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 人脸打卡
 | 
			
		||||
@ -117,4 +81,31 @@ public interface IBusAttendanceService extends IService<BusAttendance> {
 | 
			
		||||
     */
 | 
			
		||||
    Boolean punchCardByFace(MultipartFile file, BusAttendancePunchCardByFaceReq req);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据项目id查询出勤人列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param projectId 项目id
 | 
			
		||||
     * @return 出勤人列表
 | 
			
		||||
     */
 | 
			
		||||
    List<Long> listAttendancePeopleByProjectId(Long projectId);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据项目id和人员ID找到打卡范围
 | 
			
		||||
     *
 | 
			
		||||
     * @param projectId 项目id
 | 
			
		||||
     * @return 打卡范围
 | 
			
		||||
     */
 | 
			
		||||
    List<String> getPunchRangeByProjectIdAndUserId(Long projectId, Long userId);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 判断是否在打卡范围内
 | 
			
		||||
     */
 | 
			
		||||
    Boolean checkInRange(BusAttendancePunchCardByFaceReq  req);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取用户当天打卡记录
 | 
			
		||||
     */
 | 
			
		||||
    List<BusAttendanceVo> getTodayAttendance(Long projectId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,218 @@
 | 
			
		||||
package org.dromara.project.service.impl;
 | 
			
		||||
 | 
			
		||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 | 
			
		||||
import org.dromara.common.core.exception.ServiceException;
 | 
			
		||||
import org.dromara.common.core.utils.MapstructUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.dromara.project.domain.bo.BusAttendanceRuleBo;
 | 
			
		||||
import org.dromara.project.domain.vo.BusAttendanceRuleVo;
 | 
			
		||||
import org.dromara.project.domain.BusAttendanceRule;
 | 
			
		||||
import org.dromara.project.mapper.BusAttendanceRuleMapper;
 | 
			
		||||
import org.dromara.project.service.IBusAttendanceRuleService;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalTime;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤打卡规则Service业务层处理
 | 
			
		||||
 *
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Service
 | 
			
		||||
public class BusAttendanceRuleServiceImpl extends ServiceImpl<BusAttendanceRuleMapper, BusAttendanceRule>
 | 
			
		||||
    implements IBusAttendanceRuleService {
 | 
			
		||||
 | 
			
		||||
    private final BusAttendanceRuleMapper baseMapper;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询考勤打卡规则
 | 
			
		||||
     *
 | 
			
		||||
     * @param id 主键
 | 
			
		||||
     * @return 考勤打卡规则
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public BusAttendanceRuleVo queryById(Long id) {
 | 
			
		||||
        return baseMapper.selectVoById(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询考勤打卡规则列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param bo        查询条件
 | 
			
		||||
     * @param pageQuery 分页参数
 | 
			
		||||
     * @return 考勤打卡规则分页列表
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public TableDataInfo<BusAttendanceRuleVo> queryPageList(BusAttendanceRuleBo bo, PageQuery pageQuery) {
 | 
			
		||||
        LambdaQueryWrapper<BusAttendanceRule> lqw = buildQueryWrapper(bo);
 | 
			
		||||
        Page<BusAttendanceRuleVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
 | 
			
		||||
        return TableDataInfo.build(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询符合条件的考勤打卡规则列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param bo 查询条件
 | 
			
		||||
     * @return 考勤打卡规则列表
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<BusAttendanceRuleVo> queryList(BusAttendanceRuleBo bo) {
 | 
			
		||||
        LambdaQueryWrapper<BusAttendanceRule> lqw = buildQueryWrapper(bo);
 | 
			
		||||
        return baseMapper.selectVoList(lqw);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private LambdaQueryWrapper<BusAttendanceRule> buildQueryWrapper(BusAttendanceRuleBo bo) {
 | 
			
		||||
        Map<String, Object> params = bo.getParams();
 | 
			
		||||
        LambdaQueryWrapper<BusAttendanceRule> lqw = Wrappers.lambdaQuery();
 | 
			
		||||
        lqw.orderByAsc(BusAttendanceRule::getId);
 | 
			
		||||
        lqw.eq(bo.getProjectId() != null, BusAttendanceRule::getProjectId, bo.getProjectId());
 | 
			
		||||
        lqw.eq(bo.getClockInTime() != null, BusAttendanceRule::getClockInTime, bo.getClockInTime());
 | 
			
		||||
        lqw.eq(bo.getClockOutTime() != null, BusAttendanceRule::getClockOutTime, bo.getClockOutTime());
 | 
			
		||||
        lqw.eq(bo.getIsNext() != null, BusAttendanceRule::getIsNext, bo.getIsNext());
 | 
			
		||||
        lqw.eq(bo.getClockInStartTime() != null, BusAttendanceRule::getClockInStartTime, bo.getClockInStartTime());
 | 
			
		||||
        lqw.eq(bo.getClockInEndTime() != null, BusAttendanceRule::getClockInEndTime, bo.getClockInEndTime());
 | 
			
		||||
        lqw.eq(bo.getClockOutStartTime() != null, BusAttendanceRule::getClockOutStartTime, bo.getClockOutStartTime());
 | 
			
		||||
        lqw.eq(bo.getClockOutEndTime() != null, BusAttendanceRule::getClockOutEndTime, bo.getClockOutEndTime());
 | 
			
		||||
        lqw.eq(bo.getClockInResultTime() != null, BusAttendanceRule::getClockInResultTime, bo.getClockInResultTime());
 | 
			
		||||
        lqw.eq(bo.getClockOutResultTime() != null, BusAttendanceRule::getClockOutResultTime, bo.getClockOutResultTime());
 | 
			
		||||
        lqw.eq(StringUtils.isNotBlank(bo.getWeekday()), BusAttendanceRule::getWeekday, bo.getWeekday());
 | 
			
		||||
        lqw.eq(StringUtils.isNotBlank(bo.getType()), BusAttendanceRule::getType, bo.getType());
 | 
			
		||||
        return lqw;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 新增考勤打卡规则
 | 
			
		||||
     *
 | 
			
		||||
     * @param bo 考勤打卡规则
 | 
			
		||||
     * @return 是否新增成功
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Boolean insertByBo(BusAttendanceRuleBo bo) {
 | 
			
		||||
        BusAttendanceRule add = MapstructUtils.convert(bo, BusAttendanceRule.class);
 | 
			
		||||
        validEntityBeforeSave(add);
 | 
			
		||||
        setResultTime(add);
 | 
			
		||||
        boolean flag = baseMapper.insert(add) > 0;
 | 
			
		||||
        if (flag) {
 | 
			
		||||
            bo.setId(add.getId());
 | 
			
		||||
        }
 | 
			
		||||
        return flag;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 修改考勤打卡规则
 | 
			
		||||
     *
 | 
			
		||||
     * @param bo 考勤打卡规则
 | 
			
		||||
     * @return 是否修改成功
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Boolean updateByBo(BusAttendanceRuleBo bo) {
 | 
			
		||||
        BusAttendanceRule update = MapstructUtils.convert(bo, BusAttendanceRule.class);
 | 
			
		||||
        validEntityBeforeSave(update);
 | 
			
		||||
        setResultTime(update);
 | 
			
		||||
        return baseMapper.updateById(update) > 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存前的数据校验
 | 
			
		||||
     */
 | 
			
		||||
    private void validEntityBeforeSave(BusAttendanceRule entity) {
 | 
			
		||||
        //TODO 做一些数据校验,如唯一约束
 | 
			
		||||
        //暂时一个项目一个打卡规则
 | 
			
		||||
        List<BusAttendanceRule> busAttendanceRules = baseMapper.selectList(Wrappers.<BusAttendanceRule>lambdaQuery()
 | 
			
		||||
            .eq(BusAttendanceRule::getProjectId, entity.getProjectId())
 | 
			
		||||
            .ne(entity.getId() != null, BusAttendanceRule::getId, entity.getId())
 | 
			
		||||
        );
 | 
			
		||||
        if (!busAttendanceRules.isEmpty()) {
 | 
			
		||||
            throw new ServiceException("该项目已存在打卡规则");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private void setResultTime(BusAttendanceRule entity) {
 | 
			
		||||
        if (entity.getClockInEndTime() != null) {
 | 
			
		||||
            entity.setClockInResultTime(entity.getClockInEndTime());
 | 
			
		||||
        } else {
 | 
			
		||||
            // 计算上班时间和下班时间中间的时间
 | 
			
		||||
 | 
			
		||||
            LocalTime clockInTime = entity.getClockInTime();
 | 
			
		||||
            LocalTime clockOutTime = entity.getClockOutTime();
 | 
			
		||||
 | 
			
		||||
            // 如果下班时间是次日,则加24小时
 | 
			
		||||
            long clockInSeconds = clockInTime.toSecondOfDay();
 | 
			
		||||
            long clockOutSeconds = clockOutTime.toSecondOfDay();
 | 
			
		||||
 | 
			
		||||
            if (Boolean.TRUE.equals(entity.getIsNext())) {
 | 
			
		||||
                clockOutSeconds += 24 * 60 * 60; // 加24小时的秒数
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 计算中间时间(秒)
 | 
			
		||||
            long midSeconds = (clockInSeconds + clockOutSeconds) / 2;
 | 
			
		||||
 | 
			
		||||
            // 如果超过24小时,则需要处理跨天情况
 | 
			
		||||
            if (midSeconds >= 24 * 60 * 60) {
 | 
			
		||||
                midSeconds -= 24 * 60 * 60;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 转换回LocalTime
 | 
			
		||||
            LocalTime midTime = java.time.LocalTime.ofSecondOfDay(midSeconds);
 | 
			
		||||
            entity.setClockInResultTime(midTime);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (entity.getClockOutTime() != null) {
 | 
			
		||||
            entity.setClockOutResultTime(entity.getClockOutEndTime());
 | 
			
		||||
        } else {
 | 
			
		||||
            // 计算下班时间和第二天上班时间的中间时间
 | 
			
		||||
            java.time.LocalTime clockOutTime = entity.getClockOutTime();
 | 
			
		||||
            java.time.LocalTime clockInTime = entity.getClockInTime();
 | 
			
		||||
 | 
			
		||||
            // 计算下班时间到次日上班时间的中间点
 | 
			
		||||
            long clockOutSeconds = clockOutTime.toSecondOfDay();
 | 
			
		||||
            long clockInNextDaySeconds = clockInTime.toSecondOfDay() + 24 * 60 * 60; // 次日上班时间
 | 
			
		||||
 | 
			
		||||
            // 计算中间时间
 | 
			
		||||
            long midSeconds = (clockOutSeconds + clockInNextDaySeconds) / 2;
 | 
			
		||||
 | 
			
		||||
            // 转换为当天时间(如果超过24小时)
 | 
			
		||||
            if (midSeconds >= 24 * 60 * 60) {
 | 
			
		||||
                midSeconds -= 24 * 60 * 60;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            java.time.LocalTime midTime = java.time.LocalTime.ofSecondOfDay(midSeconds);
 | 
			
		||||
            entity.setClockOutResultTime(midTime);
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验并批量删除考勤打卡规则信息
 | 
			
		||||
     *
 | 
			
		||||
     * @param ids     待删除的主键集合
 | 
			
		||||
     * @param isValid 是否进行有效性校验
 | 
			
		||||
     * @return 是否删除成功
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
 | 
			
		||||
        if (isValid) {
 | 
			
		||||
            //TODO 做一些业务上的校验,判断是否需要校验
 | 
			
		||||
        }
 | 
			
		||||
        return baseMapper.deleteByIds(ids) > 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public BusAttendanceRuleVo queryByProjectId(Long projectId) {
 | 
			
		||||
        return baseMapper.selectVoOne(Wrappers.<BusAttendanceRule>lambdaQuery()
 | 
			
		||||
            .eq(BusAttendanceRule::getProjectId, projectId));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,88 +1,97 @@
 | 
			
		||||
package org.dromara.project.service.impl;
 | 
			
		||||
 | 
			
		||||
import cn.hutool.core.collection.CollUtil;
 | 
			
		||||
import cn.hutool.core.date.DateUtil;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import cn.hutool.core.collection.CollectionUtil;
 | 
			
		||||
import cn.hutool.json.JSONArray;
 | 
			
		||||
import cn.hutool.json.JSONUtil;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 | 
			
		||||
import jakarta.annotation.Resource;
 | 
			
		||||
import org.dromara.common.core.constant.DateConstant;
 | 
			
		||||
import org.dromara.common.core.constant.HttpStatus;
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.dromara.common.core.domain.R;
 | 
			
		||||
import org.dromara.common.core.enums.FormatsType;
 | 
			
		||||
import org.dromara.common.core.exception.ServiceException;
 | 
			
		||||
import org.dromara.common.core.utils.DateUtils;
 | 
			
		||||
import org.dromara.common.core.utils.ObjectUtils;
 | 
			
		||||
import org.dromara.common.core.utils.MapstructUtils;
 | 
			
		||||
import org.dromara.common.core.utils.StringUtils;
 | 
			
		||||
import org.dromara.common.domain.GeoPoint;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.TableDataInfo;
 | 
			
		||||
import org.dromara.common.mybatis.core.page.PageQuery;
 | 
			
		||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 | 
			
		||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 | 
			
		||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 | 
			
		||||
import lombok.RequiredArgsConstructor;
 | 
			
		||||
import org.dromara.common.satoken.utils.LoginHelper;
 | 
			
		||||
import org.dromara.common.utils.JSTUtil;
 | 
			
		||||
import org.dromara.contractor.domain.SubConstructionUser;
 | 
			
		||||
import org.dromara.contractor.service.ISubConstructionUserService;
 | 
			
		||||
import org.dromara.project.domain.BusAttendance;
 | 
			
		||||
import org.dromara.project.domain.BusProject;
 | 
			
		||||
import org.dromara.project.domain.BusProjectPunchrange;
 | 
			
		||||
import org.dromara.project.domain.BusProjectTeamMember;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceMonthByUserIdReq;
 | 
			
		||||
import org.dromara.project.domain.*;
 | 
			
		||||
import org.dromara.project.domain.bo.BusProjectPunchrangeBo;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendancePunchCardByFaceReq;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryReq;
 | 
			
		||||
import org.dromara.project.domain.dto.attendance.BusAttendanceQueryTwoWeekReq;
 | 
			
		||||
import org.dromara.project.domain.enums.BusAttendanceClockStatusEnum;
 | 
			
		||||
import org.dromara.project.domain.enums.BusAttendanceCommuterEnum;
 | 
			
		||||
import org.dromara.project.domain.enums.BusAttendanceStatusEnum;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceClockDateForTwoWeekVo;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceListByDay;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceMonthByUserIdVo;
 | 
			
		||||
import org.dromara.project.domain.vo.attendance.BusAttendanceVo;
 | 
			
		||||
import org.dromara.project.mapper.BusAttendanceMapper;
 | 
			
		||||
import org.dromara.project.domain.vo.BusAttendanceRuleVo;
 | 
			
		||||
import org.dromara.project.domain.vo.BusProjectPunchrangeVo;
 | 
			
		||||
import org.dromara.project.service.*;
 | 
			
		||||
import org.dromara.system.domain.vo.SysOssVo;
 | 
			
		||||
import org.dromara.system.service.ISysOssService;
 | 
			
		||||
import org.springframework.beans.BeanUtils;
 | 
			
		||||
import org.springframework.stereotype.Service;
 | 
			
		||||
import org.dromara.project.domain.bo.BusAttendanceBo;
 | 
			
		||||
import org.dromara.project.domain.vo.BusAttendanceVo;
 | 
			
		||||
import org.dromara.project.mapper.BusAttendanceMapper;
 | 
			
		||||
import org.springframework.web.multipart.MultipartFile;
 | 
			
		||||
import org.dromara.common.core.constant.HttpStatus;
 | 
			
		||||
 | 
			
		||||
import java.time.LocalDate;
 | 
			
		||||
import java.time.LocalDateTime;
 | 
			
		||||
import java.time.LocalTime;
 | 
			
		||||
import java.time.YearMonth;
 | 
			
		||||
import java.time.ZoneId;
 | 
			
		||||
import java.time.format.DateTimeFormatter;
 | 
			
		||||
import java.time.temporal.ChronoUnit;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 考勤Service业务层处理
 | 
			
		||||
 *
 | 
			
		||||
 * @author lilemy
 | 
			
		||||
 * @date 2025-04-07
 | 
			
		||||
 * @author Lion Li
 | 
			
		||||
 * @date 2025-08-05
 | 
			
		||||
 */
 | 
			
		||||
@RequiredArgsConstructor
 | 
			
		||||
@Service
 | 
			
		||||
public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, BusAttendance>
 | 
			
		||||
    implements IBusAttendanceService {
 | 
			
		||||
@Slf4j
 | 
			
		||||
public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, BusAttendance> implements IBusAttendanceService {
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private ISysOssService ossService;
 | 
			
		||||
    private final BusAttendanceMapper baseMapper;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private IBusProjectService projectService;
 | 
			
		||||
    private final ISysOssService ossService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private IBusProjectPunchrangeService projectPunchrangeService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private IBusProjectTeamMemberService projectTeamMemberService;
 | 
			
		||||
    private final IBusProjectService projectService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private ISubConstructionUserService constructionUserService;
 | 
			
		||||
 | 
			
		||||
    @Resource
 | 
			
		||||
    private IBusConstructionBlacklistService constructionBlacklistService;
 | 
			
		||||
    private final IBusProjectPunchrangeService projectPunchrangeService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private final IBusProjectTeamMemberService projectTeamMemberService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private final ISubConstructionUserService constructionUserService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private final IBusConstructionBlacklistService constructionBlacklistService;
 | 
			
		||||
 | 
			
		||||
    private final IBusAttendanceRuleService attendanceRuleService;
 | 
			
		||||
 | 
			
		||||
    private final IBusUserProjectRelevancyService userProjectRelevancyService;
 | 
			
		||||
 | 
			
		||||
    private final IBusProjectTeamService projectTeamService;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    // 出勤状态(正常、迟到、早退)
 | 
			
		||||
    private static final Set<String> ATTENDANCE_STATUS = new HashSet<>(Arrays.asList(BusAttendanceClockStatusEnum.NORMAL.getValue(),
 | 
			
		||||
        BusAttendanceClockStatusEnum.LATE.getValue(), BusAttendanceClockStatusEnum.LEAVEEARLY.getValue()));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询考勤
 | 
			
		||||
     *
 | 
			
		||||
@ -91,354 +100,102 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public BusAttendanceVo queryById(Long id) {
 | 
			
		||||
        BusAttendance attendance = this.getById(id);
 | 
			
		||||
        if (attendance == null) {
 | 
			
		||||
            throw new ServiceException("考勤信息不存在", HttpStatus.NOT_FOUND);
 | 
			
		||||
        }
 | 
			
		||||
        return this.getVo(attendance);
 | 
			
		||||
        return baseMapper.selectVoById(id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 分页查询考勤列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param req       查询条件
 | 
			
		||||
     * @param bo        查询条件
 | 
			
		||||
     * @param pageQuery 分页参数
 | 
			
		||||
     * @return 考勤分页列表
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public TableDataInfo<BusAttendanceVo> queryPageList(BusAttendanceQueryReq req, PageQuery pageQuery) {
 | 
			
		||||
        Page<BusAttendance> result = this.page(pageQuery.build(), buildQueryWrapper(req));
 | 
			
		||||
        return TableDataInfo.build(this.getVoPage(result));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询两周内的考勤列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param req 查询条件
 | 
			
		||||
     * @return 考勤列表
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<BusAttendanceClockDateForTwoWeekVo> listClockDateForTwoWeek(BusAttendanceQueryTwoWeekReq req) {
 | 
			
		||||
        Long projectId = req.getProjectId();
 | 
			
		||||
        if (projectService.getById(projectId) == null) {
 | 
			
		||||
            throw new ServiceException("项目信息不存在", HttpStatus.NOT_FOUND);
 | 
			
		||||
        }
 | 
			
		||||
        // 1. 处理日期区间
 | 
			
		||||
        Date clockDate = req.getClockDate();
 | 
			
		||||
        LocalDate startLocal;
 | 
			
		||||
        LocalDate endLocal;
 | 
			
		||||
        if (clockDate != null) {
 | 
			
		||||
            // 检查 clockDate 不能大于当前日期
 | 
			
		||||
            if (DateUtil.compare(clockDate, new Date()) > 0) {
 | 
			
		||||
                throw new ServiceException("日期不能大于当前日期", HttpStatus.BAD_REQUEST);
 | 
			
		||||
            }
 | 
			
		||||
            // 以传入的 clockDate 为结束日期
 | 
			
		||||
            endLocal = clockDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
 | 
			
		||||
        } else {
 | 
			
		||||
            // 如果未传入,则以当前日期为结束日期
 | 
			
		||||
            endLocal = LocalDate.now();
 | 
			
		||||
        }
 | 
			
		||||
        // 计算开始日期为结束日期减两周
 | 
			
		||||
        startLocal = endLocal.minusWeeks(2);
 | 
			
		||||
        // 如果数据库中的 clockDate 只存储日期(时分秒置为 00:00:00),直接转换即可
 | 
			
		||||
        Date startDate = Date.from(startLocal.atStartOfDay(ZoneId.systemDefault()).toInstant());
 | 
			
		||||
        Date endDate = Date.from(endLocal.atStartOfDay(ZoneId.systemDefault()).toInstant());
 | 
			
		||||
        // 构造查询条件 clockDate 在 [startDate, endDate] 区间内
 | 
			
		||||
        LambdaQueryWrapper<BusAttendance> lqw = new LambdaQueryWrapper<>();
 | 
			
		||||
        lqw.eq(BusAttendance::getProjectId, projectId)
 | 
			
		||||
            .eq(StringUtils.isNotEmpty(req.getUserName()), BusAttendance::getUserName, req.getUserName())
 | 
			
		||||
            .between(BusAttendance::getClockDate, startDate, endDate)
 | 
			
		||||
            .orderByDesc(BusAttendance::getClockDate);
 | 
			
		||||
        // 获取黑名单用户列表
 | 
			
		||||
        List<Long> blackList = constructionBlacklistService.queryIdListByProjectId(projectId);
 | 
			
		||||
        // 构建查询用户相关信息查询条件
 | 
			
		||||
        List<Long> userIdList = constructionUserService.lambdaQuery()
 | 
			
		||||
            .eq(SubConstructionUser::getProjectId, projectId)
 | 
			
		||||
            .eq(req.getTeamId() != null, SubConstructionUser::getTeamId, req.getTeamId())
 | 
			
		||||
            .eq(req.getTypeOfWork() != null, SubConstructionUser::getTypeOfWork, req.getTypeOfWork())
 | 
			
		||||
            .notIn(CollUtil.isNotEmpty(blackList), SubConstructionUser::getId, blackList)
 | 
			
		||||
            .list().stream().map(SubConstructionUser::getId).toList();
 | 
			
		||||
        lqw.in(CollUtil.isNotEmpty(userIdList), BusAttendance::getUserId, userIdList);
 | 
			
		||||
        Map<Date, List<BusAttendance>> dateListMap = this.list(lqw)
 | 
			
		||||
            .stream().collect(Collectors.groupingBy(BusAttendance::getClockDate));
 | 
			
		||||
        // 遍历每个日期,计算考勤状态
 | 
			
		||||
        List<BusAttendanceClockDateForTwoWeekVo> respList = new ArrayList<>();
 | 
			
		||||
        // 遍历从两周前到今天的所有日期
 | 
			
		||||
        for (LocalDate localDate = startLocal; !localDate.isAfter(endLocal); localDate = localDate.plusDays(1)) {
 | 
			
		||||
            BusAttendanceClockDateForTwoWeekVo resp = new BusAttendanceClockDateForTwoWeekVo();
 | 
			
		||||
            // 转换为 Date 类型(时分秒为 00:00:00)
 | 
			
		||||
            Date currentDate = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
 | 
			
		||||
            resp.setClockDate(currentDate);
 | 
			
		||||
            int attendance = 0;
 | 
			
		||||
            int halfAttendance = 0;
 | 
			
		||||
            int absenteeism = 0;
 | 
			
		||||
            int leave = 0;
 | 
			
		||||
            // 获取当天的考勤记录(可能为 null)
 | 
			
		||||
            List<BusAttendance> attendanceList = dateListMap.getOrDefault(currentDate, Collections.emptyList());
 | 
			
		||||
            // 按用户分组考勤记录
 | 
			
		||||
            Map<Long, List<BusAttendance>> userAttendanceMap = attendanceList.stream()
 | 
			
		||||
                .collect(Collectors.groupingBy(BusAttendance::getUserId));
 | 
			
		||||
            if (CollUtil.isNotEmpty(userAttendanceMap)) {
 | 
			
		||||
                for (List<BusAttendance> userAttendanceList : userAttendanceMap.values()) {
 | 
			
		||||
                    String clockInStatus = null;
 | 
			
		||||
                    String clockOutStatus = null;
 | 
			
		||||
                    String clockAllDayStatus = null;
 | 
			
		||||
                    // 遍历同一用户的考勤记录,分别获取上下班状态
 | 
			
		||||
                    for (BusAttendance a : userAttendanceList) {
 | 
			
		||||
                        if (BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(a.getCommuter())) {
 | 
			
		||||
                            clockInStatus = a.getClockStatus();
 | 
			
		||||
                        } else if (BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(a.getCommuter())) {
 | 
			
		||||
                            clockOutStatus = a.getClockStatus();
 | 
			
		||||
                        } else if (BusAttendanceCommuterEnum.ALLDAY.getValue().equals(a.getCommuter())) {
 | 
			
		||||
                            clockAllDayStatus = a.getClockStatus();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    // 统计考勤状态
 | 
			
		||||
                    if (BusAttendanceClockStatusEnum.LEAVE.getValue().equals(clockAllDayStatus)) {
 | 
			
		||||
                        leave++;
 | 
			
		||||
                    } else if ((BusAttendanceClockStatusEnum.NORMAL.getValue().equals(clockInStatus)
 | 
			
		||||
                        || BusAttendanceClockStatusEnum.REISSUE.getValue().equals(clockInStatus))
 | 
			
		||||
                        && (BusAttendanceClockStatusEnum.NORMAL.getValue().equals(clockOutStatus)
 | 
			
		||||
                        || BusAttendanceClockStatusEnum.REISSUE.getValue().equals(clockOutStatus))) {
 | 
			
		||||
                        attendance++;
 | 
			
		||||
                    } else if (BusAttendanceClockStatusEnum.UNCLOCK.getValue().equals(clockInStatus)
 | 
			
		||||
                        && BusAttendanceClockStatusEnum.UNCLOCK.getValue().equals(clockOutStatus)) {
 | 
			
		||||
                        absenteeism++;
 | 
			
		||||
                    } else if (BusAttendanceClockStatusEnum.UNCLOCK.getValue().equals(clockInStatus)
 | 
			
		||||
                        || BusAttendanceClockStatusEnum.UNCLOCK.getValue().equals(clockOutStatus)) {
 | 
			
		||||
                        halfAttendance++;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            resp.setAttendance(attendance);
 | 
			
		||||
            resp.setHalfAttendance(halfAttendance);
 | 
			
		||||
            resp.setAbsenteeism(absenteeism);
 | 
			
		||||
            resp.setLeave(leave);
 | 
			
		||||
            respList.add(resp);
 | 
			
		||||
        }
 | 
			
		||||
        // 按打卡日期正序排列
 | 
			
		||||
        respList.sort(Comparator.comparing(BusAttendanceClockDateForTwoWeekVo::getClockDate));
 | 
			
		||||
        return respList;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询用户每月考勤列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param req 查询条件
 | 
			
		||||
     * @return 考勤列表
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<BusAttendanceMonthByUserIdVo> listAttendanceMonthListByUserId(BusAttendanceMonthByUserIdReq req) {
 | 
			
		||||
        Long userId = req.getUserId();
 | 
			
		||||
        String clockMonth = req.getClockMonth();
 | 
			
		||||
        if (constructionUserService.getById(userId) == null) {
 | 
			
		||||
            throw new ServiceException("施工人员信息不存在", HttpStatus.NOT_FOUND);
 | 
			
		||||
        }
 | 
			
		||||
        // 解析月份
 | 
			
		||||
        YearMonth yearMonth;
 | 
			
		||||
        if (StringUtils.isNotBlank(clockMonth)) {
 | 
			
		||||
            // 校验月份格式
 | 
			
		||||
            if (!DateConstant.YEAR_MONTH_PATTERN.matcher(clockMonth).matches()) {
 | 
			
		||||
                throw new ServiceException("月份格式不正确", HttpStatus.BAD_REQUEST);
 | 
			
		||||
            }
 | 
			
		||||
            yearMonth = YearMonth.parse(clockMonth);
 | 
			
		||||
        } else {
 | 
			
		||||
            // 如果月份为空,则默认查询当前月份
 | 
			
		||||
            yearMonth = YearMonth.now();
 | 
			
		||||
        }
 | 
			
		||||
        // 计算当月第一天 / 最后一天
 | 
			
		||||
        Date start = DateUtils.toDate(yearMonth.atDay(1));
 | 
			
		||||
        Date end = DateUtils.toDate(yearMonth.atEndOfMonth());
 | 
			
		||||
        // 查询当月考勤记录
 | 
			
		||||
        Map<Date, List<BusAttendance>> dateListMap = this.lambdaQuery()
 | 
			
		||||
            .eq(BusAttendance::getUserId, userId)
 | 
			
		||||
            .between(BusAttendance::getClockDate, start, end)
 | 
			
		||||
            .list()
 | 
			
		||||
            .stream().collect(Collectors.groupingBy(BusAttendance::getClockDate));
 | 
			
		||||
        // 遍历每天,计算考勤状态
 | 
			
		||||
        List<BusAttendanceMonthByUserIdVo> respList = new ArrayList<>();
 | 
			
		||||
        dateListMap.forEach((date, attendanceList) -> {
 | 
			
		||||
            BusAttendanceMonthByUserIdVo resp = new BusAttendanceMonthByUserIdVo();
 | 
			
		||||
            resp.setId(userId);
 | 
			
		||||
            resp.setClockDate(date);
 | 
			
		||||
            List<BusAttendanceListByDay> attendanceListByDayList = new ArrayList<>();
 | 
			
		||||
            String clockInStatus = null;
 | 
			
		||||
            String clockOutStatus = null;
 | 
			
		||||
            String clockAllDayStatus = null;
 | 
			
		||||
            String status;
 | 
			
		||||
            for (BusAttendance attendance : attendanceList) {
 | 
			
		||||
                // 获取考勤记录
 | 
			
		||||
                BusAttendanceListByDay day = BusAttendanceListByDay.build(attendance);
 | 
			
		||||
                attendanceListByDayList.add(day);
 | 
			
		||||
                // 获取上下班状态
 | 
			
		||||
                if (BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(attendance.getCommuter())) {
 | 
			
		||||
                    clockInStatus = attendance.getClockStatus();
 | 
			
		||||
                } else if (BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(attendance.getCommuter())) {
 | 
			
		||||
                    clockOutStatus = attendance.getClockStatus();
 | 
			
		||||
                } else {
 | 
			
		||||
                    clockAllDayStatus = attendance.getClockStatus();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // 统计当天考勤状态
 | 
			
		||||
            if (BusAttendanceClockStatusEnum.LEAVE.getValue().equals(clockAllDayStatus)) {
 | 
			
		||||
                return;
 | 
			
		||||
            } else if (BusAttendanceClockStatusEnum.NORMAL.getValue().equals(clockInStatus)
 | 
			
		||||
                && BusAttendanceClockStatusEnum.NORMAL.getValue().equals(clockOutStatus)) {
 | 
			
		||||
                status = BusAttendanceStatusEnum.NORMAL.getValue();
 | 
			
		||||
            } else if (BusAttendanceClockStatusEnum.REISSUE.getValue().equals(clockInStatus)
 | 
			
		||||
                || BusAttendanceClockStatusEnum.REISSUE.getValue().equals(clockOutStatus)) {
 | 
			
		||||
                status = BusAttendanceStatusEnum.REISSUE.getValue();
 | 
			
		||||
            } else {
 | 
			
		||||
                status = BusAttendanceStatusEnum.ERROR.getValue();
 | 
			
		||||
            }
 | 
			
		||||
            resp.setStatus(status);
 | 
			
		||||
            resp.setAttendanceList(attendanceListByDayList);
 | 
			
		||||
            respList.add(resp);
 | 
			
		||||
        });
 | 
			
		||||
        // 按打卡日期正序排列
 | 
			
		||||
        respList.sort(Comparator.comparing(BusAttendanceMonthByUserIdVo::getClockDate));
 | 
			
		||||
        return respList;
 | 
			
		||||
    public TableDataInfo<BusAttendanceVo> queryPageList(BusAttendanceBo bo, PageQuery pageQuery) {
 | 
			
		||||
        LambdaQueryWrapper<BusAttendance> lqw = buildQueryWrapper(bo);
 | 
			
		||||
        Page<BusAttendanceVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
 | 
			
		||||
        return TableDataInfo.build(result);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 查询符合条件的考勤列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param req 查询条件
 | 
			
		||||
     * @param bo 查询条件
 | 
			
		||||
     * @return 考勤列表
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<BusAttendanceVo> queryList(BusAttendanceQueryReq req) {
 | 
			
		||||
        LambdaQueryWrapper<BusAttendance> lqw = buildQueryWrapper(req);
 | 
			
		||||
        return this.list(lqw).stream().map(this::getVo).toList();
 | 
			
		||||
    public List<BusAttendanceVo> queryList(BusAttendanceBo bo) {
 | 
			
		||||
        LambdaQueryWrapper<BusAttendance> lqw = buildQueryWrapper(bo);
 | 
			
		||||
        return baseMapper.selectVoList(lqw);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据项目id查询出勤人列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param projectId 项目id
 | 
			
		||||
     * @return 出勤人列表
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Long> listAttendancePeopleByProjectId(Long projectId) {
 | 
			
		||||
        // 今天所有用户的打卡记录
 | 
			
		||||
        List<BusAttendance> attendanceList = this.lambdaQuery()
 | 
			
		||||
            .eq(BusAttendance::getProjectId, projectId)
 | 
			
		||||
            .eq(BusAttendance::getClockDate, DateUtils.dateTimeNow(FormatsType.YYYY_MM_DD))
 | 
			
		||||
            .list();
 | 
			
		||||
        if (CollUtil.isEmpty(attendanceList)) {
 | 
			
		||||
            return List.of();
 | 
			
		||||
        }
 | 
			
		||||
        Map<Long, List<BusAttendance>> attendanceMap = attendanceList.stream()
 | 
			
		||||
            .collect(Collectors.groupingBy(BusAttendance::getUserId));
 | 
			
		||||
        List<Long> attendedUserIds = new ArrayList<>();
 | 
			
		||||
        for (Map.Entry<Long, List<BusAttendance>> entry : attendanceMap.entrySet()) {
 | 
			
		||||
            Long userId = entry.getKey();
 | 
			
		||||
            List<BusAttendance> records = entry.getValue();
 | 
			
		||||
            // 要求必须有2条打卡记录
 | 
			
		||||
            if (records.size() != 2) {
 | 
			
		||||
                continue;
 | 
			
		||||
            }
 | 
			
		||||
            boolean allValid = records.stream()
 | 
			
		||||
                .allMatch(record -> ATTENDANCE_STATUS.contains(record.getClockStatus()));
 | 
			
		||||
            if (allValid) {
 | 
			
		||||
                attendedUserIds.add(userId);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return attendedUserIds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取考勤视图对象
 | 
			
		||||
     *
 | 
			
		||||
     * @param attendance 考勤对象
 | 
			
		||||
     * @return 考勤视图对象
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public BusAttendanceVo getVo(BusAttendance attendance) {
 | 
			
		||||
        // 对象转封装类
 | 
			
		||||
        BusAttendanceVo attendanceVo = new BusAttendanceVo();
 | 
			
		||||
        if (attendance == null) {
 | 
			
		||||
            return attendanceVo;
 | 
			
		||||
        }
 | 
			
		||||
        BeanUtils.copyProperties(attendance, attendanceVo);
 | 
			
		||||
        return attendanceVo;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取考勤查询条件封装
 | 
			
		||||
     *
 | 
			
		||||
     * @param req 查询条件
 | 
			
		||||
     * @return 查询条件封装
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public LambdaQueryWrapper<BusAttendance> buildQueryWrapper(BusAttendanceQueryReq req) {
 | 
			
		||||
        LambdaQueryWrapper<BusAttendance> lqw = new LambdaQueryWrapper<>();
 | 
			
		||||
        if (req == null) {
 | 
			
		||||
            return lqw;
 | 
			
		||||
        }
 | 
			
		||||
        String userName = req.getUserName();
 | 
			
		||||
        Long projectId = req.getProjectId();
 | 
			
		||||
        Long teamId = req.getTeamId();
 | 
			
		||||
        String clockStatus = req.getClockStatus();
 | 
			
		||||
        Date clockDate = req.getClockDate();
 | 
			
		||||
        // 联表查询
 | 
			
		||||
        if (ObjectUtils.isNotEmpty(teamId)) {
 | 
			
		||||
            List<BusProjectTeamMember> projectTeamMemberList = projectTeamMemberService.lambdaQuery()
 | 
			
		||||
                .eq(BusProjectTeamMember::getTeamId, teamId).list();
 | 
			
		||||
            List<Long> userIdList = projectTeamMemberList.stream().map(BusProjectTeamMember::getMemberId).toList();
 | 
			
		||||
            lqw.in(BusAttendance::getUserId, userIdList);
 | 
			
		||||
        }
 | 
			
		||||
        // 模糊查询
 | 
			
		||||
        lqw.like(StringUtils.isNotBlank(userName), BusAttendance::getUserName, userName);
 | 
			
		||||
        // 精确查询
 | 
			
		||||
        lqw.eq(ObjectUtils.isNotEmpty(projectId), BusAttendance::getProjectId, projectId);
 | 
			
		||||
        lqw.eq(StringUtils.isNotBlank(clockStatus), BusAttendance::getClockStatus, clockStatus);
 | 
			
		||||
        lqw.eq(ObjectUtils.isNotEmpty(clockDate), BusAttendance::getClockDate, clockDate);
 | 
			
		||||
        // 不包含请假的考勤记录
 | 
			
		||||
        lqw.ne(BusAttendance::getClockStatus, BusAttendanceClockStatusEnum.LEAVE.getValue());
 | 
			
		||||
    private LambdaQueryWrapper<BusAttendance> buildQueryWrapper(BusAttendanceBo bo) {
 | 
			
		||||
        Map<String, Object> params = bo.getParams();
 | 
			
		||||
        LambdaQueryWrapper<BusAttendance> lqw = Wrappers.lambdaQuery();
 | 
			
		||||
        lqw.orderByAsc(BusAttendance::getId);
 | 
			
		||||
        lqw.eq(bo.getUserId() != null, BusAttendance::getUserId, bo.getUserId());
 | 
			
		||||
        lqw.eq(StringUtils.isNotBlank(bo.getFacePic()), BusAttendance::getFacePic, bo.getFacePic());
 | 
			
		||||
        lqw.eq(bo.getProjectId() != null, BusAttendance::getProjectId, bo.getProjectId());
 | 
			
		||||
        lqw.eq(bo.getClockDate() != null, BusAttendance::getClockDate, bo.getClockDate());
 | 
			
		||||
        lqw.eq(bo.getClockTime() != null, BusAttendance::getClockTime, bo.getClockTime());
 | 
			
		||||
        lqw.eq(StringUtils.isNotBlank(bo.getClockStatus()), BusAttendance::getClockStatus, bo.getClockStatus());
 | 
			
		||||
        lqw.eq(StringUtils.isNotBlank(bo.getClockType()), BusAttendance::getClockType, bo.getClockType());
 | 
			
		||||
        lqw.eq(StringUtils.isNotBlank(bo.getClockLocation()), BusAttendance::getClockLocation, bo.getClockLocation());
 | 
			
		||||
        lqw.eq(StringUtils.isNotBlank(bo.getLng()), BusAttendance::getLng, bo.getLng());
 | 
			
		||||
        lqw.eq(StringUtils.isNotBlank(bo.getLat()), BusAttendance::getLat, bo.getLat());
 | 
			
		||||
        return lqw;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 获取考勤分页对象视图
 | 
			
		||||
     * 新增考勤
 | 
			
		||||
     *
 | 
			
		||||
     * @param attendancePage 考勤分页对象
 | 
			
		||||
     * @return 考勤分页对象视图
 | 
			
		||||
     * @param bo 考勤
 | 
			
		||||
     * @return 是否新增成功
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Page<BusAttendanceVo> getVoPage(Page<BusAttendance> attendancePage) {
 | 
			
		||||
        List<BusAttendance> attendanceList = attendancePage.getRecords();
 | 
			
		||||
        Page<BusAttendanceVo> attendanceVoPage = new Page<>(
 | 
			
		||||
            attendancePage.getCurrent(),
 | 
			
		||||
            attendancePage.getSize(),
 | 
			
		||||
            attendancePage.getTotal()
 | 
			
		||||
        );
 | 
			
		||||
        if (CollUtil.isEmpty(attendanceList)) {
 | 
			
		||||
            return attendanceVoPage;
 | 
			
		||||
    public Boolean insertByBo(BusAttendanceBo bo) {
 | 
			
		||||
        BusAttendance add = MapstructUtils.convert(bo, BusAttendance.class);
 | 
			
		||||
        validEntityBeforeSave(add);
 | 
			
		||||
        boolean flag = baseMapper.insert(add) > 0;
 | 
			
		||||
        if (flag) {
 | 
			
		||||
            bo.setId(add.getId());
 | 
			
		||||
        }
 | 
			
		||||
        List<BusAttendanceVo> attendanceVoList = attendanceList.stream().map(this::getVo).toList();
 | 
			
		||||
        attendanceVoPage.setRecords(attendanceVoList);
 | 
			
		||||
        return attendanceVoPage;
 | 
			
		||||
        return flag;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据系统用户id和日期查询考勤
 | 
			
		||||
     * 修改考勤
 | 
			
		||||
     *
 | 
			
		||||
     * @param userId    用户id
 | 
			
		||||
     * @param clockDate 日期
 | 
			
		||||
     * @return 考勤
 | 
			
		||||
     * @param bo 考勤
 | 
			
		||||
     * @return 是否修改成功
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<BusAttendanceVo> getDayByUserId(Long userId, Date clockDate) {
 | 
			
		||||
        SubConstructionUser constructionUser = constructionUserService.getBySysUserId(userId);
 | 
			
		||||
        // 当日期未指定时,默认为今天
 | 
			
		||||
        if (clockDate == null) {
 | 
			
		||||
            clockDate = DateUtils.parseDateTime(FormatsType.YYYY_MM_DD, DateUtils.getDate());
 | 
			
		||||
    public Boolean updateByBo(BusAttendanceBo bo) {
 | 
			
		||||
        BusAttendance update = MapstructUtils.convert(bo, BusAttendance.class);
 | 
			
		||||
        validEntityBeforeSave(update);
 | 
			
		||||
        return baseMapper.updateById(update) > 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 保存前的数据校验
 | 
			
		||||
     */
 | 
			
		||||
    private void validEntityBeforeSave(BusAttendance entity) {
 | 
			
		||||
        //TODO 做一些数据校验,如唯一约束
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 校验并批量删除考勤信息
 | 
			
		||||
     *
 | 
			
		||||
     * @param ids     待删除的主键集合
 | 
			
		||||
     * @param isValid 是否进行有效性校验
 | 
			
		||||
     * @return 是否删除成功
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
 | 
			
		||||
        if (isValid) {
 | 
			
		||||
            //TODO 做一些业务上的校验,判断是否需要校验
 | 
			
		||||
        }
 | 
			
		||||
        LambdaQueryWrapper<BusAttendance> queryWrapper = new LambdaQueryWrapper<>();
 | 
			
		||||
        queryWrapper.eq(BusAttendance::getUserId, constructionUser.getId())
 | 
			
		||||
            .eq(BusAttendance::getClockDate, clockDate);
 | 
			
		||||
        return this.list(queryWrapper).stream().map(this::getVo).toList();
 | 
			
		||||
        return baseMapper.deleteByIds(ids) > 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@ -454,22 +211,15 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
 | 
			
		||||
        Long userId = LoginHelper.getUserId();
 | 
			
		||||
        synchronized (userId.toString().intern()) {
 | 
			
		||||
            // 记录当前打卡时间
 | 
			
		||||
            LocalTime now = LocalTime.now();
 | 
			
		||||
            Date nowDate = new Date();
 | 
			
		||||
            // 获取坐标
 | 
			
		||||
            String lat = req.getLat();
 | 
			
		||||
            String lng = req.getLng();
 | 
			
		||||
            // 校验用户是否合法
 | 
			
		||||
            LocalDateTime now = LocalDateTime.now();
 | 
			
		||||
 | 
			
		||||
            //打卡范围
 | 
			
		||||
            if (!checkInRange(req)) {
 | 
			
		||||
                throw new ServiceException("打卡位置不在范围内", HttpStatus.BAD_REQUEST);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //用户信息校验
 | 
			
		||||
            SubConstructionUser constructionUser = constructionUserService.getBySysUserId(userId);
 | 
			
		||||
            Long projectId = constructionUser.getProjectId();
 | 
			
		||||
            if (projectId == null) {
 | 
			
		||||
                throw new ServiceException("当前用户未加入项目", HttpStatus.BAD_REQUEST);
 | 
			
		||||
            }
 | 
			
		||||
            BusProject project = projectService.getById(projectId);
 | 
			
		||||
            Long teamId = constructionUser.getTeamId();
 | 
			
		||||
            if (teamId == null) {
 | 
			
		||||
                throw new ServiceException("当前用户未加入班组", HttpStatus.BAD_REQUEST);
 | 
			
		||||
            }
 | 
			
		||||
            final String status = "1";
 | 
			
		||||
            if (constructionUser.getStatus().equals(status)) {
 | 
			
		||||
                throw new ServiceException("当前用户已离职", HttpStatus.BAD_REQUEST);
 | 
			
		||||
@ -478,60 +228,46 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
 | 
			
		||||
            if (constructionUser.getClock().equals(noClock)) {
 | 
			
		||||
                throw new ServiceException("当前用户已被禁止打卡", HttpStatus.BAD_REQUEST);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // 判断用户是否已经被拉黑
 | 
			
		||||
            constructionBlacklistService.validUserInBlacklist(constructionUser.getId(), projectId);
 | 
			
		||||
            // todo 地理位置校验
 | 
			
		||||
            List<BusProjectPunchrange> punchranges = projectPunchrangeService.lambdaQuery()
 | 
			
		||||
                .eq(BusProjectPunchrange::getProjectId, projectId)
 | 
			
		||||
                .list();
 | 
			
		||||
            if (CollUtil.isEmpty(punchranges)) {
 | 
			
		||||
                throw new ServiceException("项目未配置考勤范围", HttpStatus.BAD_REQUEST);
 | 
			
		||||
            }
 | 
			
		||||
            List<String> punchRangeList = punchranges.stream().map(BusProjectPunchrange::getPunchRange).toList();
 | 
			
		||||
            List<GeoPoint> matchingRange = JSTUtil.findMatchingRange(lat, lng, punchRangeList);
 | 
			
		||||
            if (matchingRange == null) {
 | 
			
		||||
                throw new ServiceException("打卡位置不在范围内", HttpStatus.BAD_REQUEST);
 | 
			
		||||
            }
 | 
			
		||||
            constructionBlacklistService.validUserInBlacklist(constructionUser.getId(), req.getProjectId());
 | 
			
		||||
 | 
			
		||||
            // 进行人脸比对
 | 
			
		||||
            Boolean result = constructionUserService.faceComparison(file);
 | 
			
		||||
            if (!result) {
 | 
			
		||||
                throw new ServiceException("人脸识别失败,请重新识别", HttpStatus.BAD_REQUEST);
 | 
			
		||||
            }
 | 
			
		||||
            // 判断打卡状态
 | 
			
		||||
            String punchRange = project.getPunchRange();
 | 
			
		||||
            if (punchRange == null) {
 | 
			
		||||
                throw new ServiceException("未设置打卡时间范围", HttpStatus.BAD_REQUEST);
 | 
			
		||||
 | 
			
		||||
            //打卡规则
 | 
			
		||||
            BusAttendanceRuleVo busAttendanceRuleVo = attendanceRuleService.queryByProjectId(req.getProjectId());
 | 
			
		||||
 | 
			
		||||
            if (busAttendanceRuleVo == null) {
 | 
			
		||||
                throw new ServiceException("未设置打卡规则", HttpStatus.BAD_REQUEST);
 | 
			
		||||
            }
 | 
			
		||||
            String[] time = punchRange.split(",");
 | 
			
		||||
            // 解析字符串为 LocalTime
 | 
			
		||||
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm");
 | 
			
		||||
            LocalTime startTime = LocalTime.parse(time[0], formatter);
 | 
			
		||||
            LocalTime endTime = LocalTime.parse(time[1], formatter);
 | 
			
		||||
            Date date = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant());
 | 
			
		||||
 | 
			
		||||
            // 考勤时间
 | 
			
		||||
            //确定考勤日期
 | 
			
		||||
            LocalDate localDate = calculateAttendanceDate(now, busAttendanceRuleVo);
 | 
			
		||||
 | 
			
		||||
            // 判断当前用户打卡状态
 | 
			
		||||
            List<BusAttendance> attendances = this.lambdaQuery()
 | 
			
		||||
                .eq(BusAttendance::getUserId, userId)
 | 
			
		||||
                .eq(BusAttendance::getClockDate, date)
 | 
			
		||||
                .eq(BusAttendance::getClockDate, localDate)
 | 
			
		||||
                .list();
 | 
			
		||||
            BusAttendance attendance = new BusAttendance();
 | 
			
		||||
            if (CollUtil.isEmpty(attendances)) {
 | 
			
		||||
                // 上班打卡
 | 
			
		||||
                attendance.setCommuter(BusAttendanceCommuterEnum.CLOCKIN.getValue());
 | 
			
		||||
                // 上传人脸照
 | 
			
		||||
                SysOssVo upload = ossService.upload(file);
 | 
			
		||||
                attendance.setFacePic(upload.getOssId().toString());
 | 
			
		||||
                attendance.setClockType(BusAttendanceCommuterEnum.CLOCKIN.getValue());
 | 
			
		||||
                // 判断是否为迟到
 | 
			
		||||
                if (now.isAfter(startTime)) {
 | 
			
		||||
                if (isLate(now, busAttendanceRuleVo)) {
 | 
			
		||||
                    attendance.setClockStatus(BusAttendanceClockStatusEnum.LATE.getValue());
 | 
			
		||||
                } else {
 | 
			
		||||
                    attendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue());
 | 
			
		||||
                }
 | 
			
		||||
            } else if (attendances.size() == 1 && attendances.getFirst().getCommuter().equals(BusAttendanceCommuterEnum.CLOCKIN.getValue())) {
 | 
			
		||||
            } else if (attendances.size() == 1) {
 | 
			
		||||
                // 下班打卡
 | 
			
		||||
                attendance.setCommuter(BusAttendanceCommuterEnum.CLOCKOUT.getValue());
 | 
			
		||||
                attendance.setClockType(BusAttendanceCommuterEnum.CLOCKOUT.getValue());
 | 
			
		||||
                // 判断是否为早退
 | 
			
		||||
                if (now.isBefore(endTime)) {
 | 
			
		||||
                if (isLeaveEarly(now, busAttendanceRuleVo)) {
 | 
			
		||||
                    attendance.setClockStatus(BusAttendanceClockStatusEnum.LEAVEEARLY.getValue());
 | 
			
		||||
                } else {
 | 
			
		||||
                    attendance.setClockStatus(BusAttendanceClockStatusEnum.NORMAL.getValue());
 | 
			
		||||
@ -543,19 +279,185 @@ public class BusAttendanceServiceImpl extends ServiceImpl<BusAttendanceMapper, B
 | 
			
		||||
            }
 | 
			
		||||
            // 填充信息
 | 
			
		||||
            attendance.setUserId(userId);
 | 
			
		||||
            attendance.setUserName(constructionUser.getUserName());
 | 
			
		||||
            attendance.setProjectId(projectId);
 | 
			
		||||
            attendance.setClockDate(date);
 | 
			
		||||
            attendance.setClockTime(nowDate);
 | 
			
		||||
            attendance.setProjectId(req.getProjectId());
 | 
			
		||||
            attendance.setClockDate(localDate);
 | 
			
		||||
            attendance.setClockTime(now);
 | 
			
		||||
            // 记录打卡坐标
 | 
			
		||||
            attendance.setLat(lat);
 | 
			
		||||
            attendance.setLng(lng);
 | 
			
		||||
            attendance.setPunchRange(matchingRange.toString());
 | 
			
		||||
            attendance.setLat(req.getLat());
 | 
			
		||||
            attendance.setLng(req.getLng());
 | 
			
		||||
            attendance.setClockLocation(req.getLocationName());
 | 
			
		||||
            // 上传人脸照
 | 
			
		||||
            SysOssVo upload = ossService.upload(file);
 | 
			
		||||
            attendance.setFacePic(upload.getOssId().toString());
 | 
			
		||||
            return true;
 | 
			
		||||
            return this.save(attendance);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 根据项目id查询出勤人列表
 | 
			
		||||
     *
 | 
			
		||||
     * @param projectId 项目id
 | 
			
		||||
     * @return 出勤人列表
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<Long> listAttendancePeopleByProjectId(Long projectId) {
 | 
			
		||||
        // 今天所有用户的打卡记录
 | 
			
		||||
        List<BusAttendance> attendanceList = this.lambdaQuery()
 | 
			
		||||
            .eq(BusAttendance::getProjectId, projectId)
 | 
			
		||||
            .eq(BusAttendance::getClockDate, LocalDate.now())
 | 
			
		||||
            .list();
 | 
			
		||||
        if (CollUtil.isEmpty(attendanceList)) {
 | 
			
		||||
            return List.of();
 | 
			
		||||
        }
 | 
			
		||||
        Map<Long, List<BusAttendance>> attendanceMap = attendanceList.stream()
 | 
			
		||||
            .collect(Collectors.groupingBy(BusAttendance::getUserId));
 | 
			
		||||
        List<Long> attendedUserIds = new ArrayList<>();
 | 
			
		||||
        for (Map.Entry<Long, List<BusAttendance>> entry : attendanceMap.entrySet()) {
 | 
			
		||||
            Long userId = entry.getKey();
 | 
			
		||||
            List<BusAttendance> records = entry.getValue();
 | 
			
		||||
 | 
			
		||||
            boolean allValid = records.stream()
 | 
			
		||||
                .allMatch(record -> ATTENDANCE_STATUS.contains(record.getClockStatus()));
 | 
			
		||||
            if (allValid) {
 | 
			
		||||
                attendedUserIds.add(userId);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return attendedUserIds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<String> getPunchRangeByProjectIdAndUserId(Long projectId, Long userId) {
 | 
			
		||||
        BusUserProjectRelevancy relevancy = userProjectRelevancyService.getOne(Wrappers.lambdaQuery(BusUserProjectRelevancy.class)
 | 
			
		||||
            .eq(BusUserProjectRelevancy::getUserId, userId)
 | 
			
		||||
            .eq(BusUserProjectRelevancy::getProjectId, projectId)
 | 
			
		||||
            .last("limit 1"));
 | 
			
		||||
        if (relevancy == null) {
 | 
			
		||||
            throw new ServiceException("当前用户未加入项目", HttpStatus.BAD_REQUEST);
 | 
			
		||||
        }
 | 
			
		||||
        //判断是否是管理员 管理员返回项目全部打卡范围,施工人员返回班组打卡范围
 | 
			
		||||
        boolean isAdmin = "2".equals(relevancy.getUserType());
 | 
			
		||||
        List<Long> rangeIds = new ArrayList<>();
 | 
			
		||||
        if (!isAdmin) {
 | 
			
		||||
            BusProjectTeamMember one = projectTeamMemberService.getOne(Wrappers.lambdaQuery(BusProjectTeamMember.class)
 | 
			
		||||
                .eq(BusProjectTeamMember::getMemberId, userId)
 | 
			
		||||
                .eq(BusProjectTeamMember::getProjectId, projectId)
 | 
			
		||||
                .last("limit 1"));
 | 
			
		||||
            if (one == null) {
 | 
			
		||||
                throw new ServiceException("当前用户未加入班组", HttpStatus.BAD_REQUEST);
 | 
			
		||||
            }
 | 
			
		||||
            BusProjectTeam team = projectTeamService.getById(one.getTeamId());
 | 
			
		||||
            try {
 | 
			
		||||
                JSONArray jsonArray = JSONUtil.parseArray(team.getPunchRange());
 | 
			
		||||
                rangeIds = jsonArray.toList(Long.class);
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return projectPunchrangeService.lambdaQuery()
 | 
			
		||||
            .in(CollectionUtil.isNotEmpty(rangeIds), BusProjectPunchrange::getId, rangeIds)
 | 
			
		||||
            .eq(BusProjectPunchrange::getProjectId, projectId)
 | 
			
		||||
            .list()
 | 
			
		||||
            .stream()
 | 
			
		||||
            .map(BusProjectPunchrange::getPunchRange)
 | 
			
		||||
            .toList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Boolean checkInRange(BusAttendancePunchCardByFaceReq req) {
 | 
			
		||||
        // 获取当前用户
 | 
			
		||||
        Long userId = LoginHelper.getUserId();
 | 
			
		||||
        List<String> punchRangeList = getPunchRangeByProjectIdAndUserId(req.getProjectId(), userId);
 | 
			
		||||
        if (CollUtil.isEmpty(punchRangeList)) {
 | 
			
		||||
            throw new ServiceException("项目未配置考勤范围", HttpStatus.BAD_REQUEST);
 | 
			
		||||
        }
 | 
			
		||||
        List<GeoPoint> matchingRange = JSTUtil.findMatchingRange(req.getLat(), req.getLng(), punchRangeList);
 | 
			
		||||
        return matchingRange !=  null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public List<BusAttendanceVo> getTodayAttendance( Long projectId) {
 | 
			
		||||
 | 
			
		||||
        Long userId = LoginHelper.getUserId();
 | 
			
		||||
        BusAttendanceRuleVo busAttendanceRuleVo = attendanceRuleService.queryByProjectId(projectId);
 | 
			
		||||
 | 
			
		||||
        if (busAttendanceRuleVo == null) {
 | 
			
		||||
            return List.of();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // 考勤时间
 | 
			
		||||
        //确定考勤日期
 | 
			
		||||
        LocalDate localDate = calculateAttendanceDate(LocalDateTime.now(), busAttendanceRuleVo);
 | 
			
		||||
 | 
			
		||||
        return baseMapper.selectVoList(new LambdaQueryWrapper<BusAttendance>()
 | 
			
		||||
                .eq(BusAttendance::getUserId, userId)
 | 
			
		||||
                .eq(BusAttendance::getClockDate, localDate)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 计算打卡时间归属的考勤日(关键:解决跨天场景的日期映射)
 | 
			
		||||
     */
 | 
			
		||||
    private LocalDate calculateAttendanceDate(LocalDateTime checkTime, BusAttendanceRuleVo vo) {
 | 
			
		||||
        LocalTime clockInTime = vo.getClockInTime();
 | 
			
		||||
        LocalTime clockOutResultTime = vo.getClockOutResultTime();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        //一共有四个节点即 上班-上下中间-下班-下上中间-第二天上班
 | 
			
		||||
        // 下上中间 就是日期分割点
 | 
			
		||||
 | 
			
		||||
        // 跨天场景:以切换小时为界(如3点)
 | 
			
		||||
        LocalDateTime checkDateSwitchPoint = LocalDateTime.of(checkTime.toLocalDate(), clockOutResultTime);
 | 
			
		||||
 | 
			
		||||
        if (clockOutResultTime.isBefore(clockInTime)) {
 | 
			
		||||
            return checkTime.isBefore(checkDateSwitchPoint)
 | 
			
		||||
                ? checkTime.toLocalDate().minusDays(1)
 | 
			
		||||
                : checkTime.toLocalDate();
 | 
			
		||||
        }
 | 
			
		||||
        if (clockOutResultTime.isAfter(clockInTime)) {
 | 
			
		||||
            return checkTime.isBefore(checkDateSwitchPoint)
 | 
			
		||||
                ? checkTime.toLocalDate()
 | 
			
		||||
                : checkTime.toLocalDate().plusDays(1);
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 判断是否迟到
 | 
			
		||||
     */
 | 
			
		||||
    private Boolean isLate(LocalDateTime checkTime, BusAttendanceRuleVo vo) {
 | 
			
		||||
 | 
			
		||||
        long clockInSeconds = vo.getClockInTime().toSecondOfDay();
 | 
			
		||||
        long clockInResultSeconds = vo.getClockInResultTime().toSecondOfDay();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        if (vo.getClockInResultTime().isBefore(vo.getClockInTime())) {
 | 
			
		||||
            clockInResultSeconds += 24 * 60 * 60; // 加24小时的秒数
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        long localTime = checkTime.toLocalTime().toSecondOfDay();
 | 
			
		||||
 | 
			
		||||
        return localTime > clockInSeconds && localTime < clockInResultSeconds;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 判断是否早退
 | 
			
		||||
     */
 | 
			
		||||
    private Boolean isLeaveEarly(LocalDateTime checkTime, BusAttendanceRuleVo vo) {
 | 
			
		||||
        long clockOutSeconds = vo.getClockOutTime().toSecondOfDay();
 | 
			
		||||
        long clockOutResultSeconds = vo.getClockOutResultTime().toSecondOfDay();
 | 
			
		||||
 | 
			
		||||
        if (vo.getClockOutResultTime().isBefore(vo.getClockOutTime())) {
 | 
			
		||||
            clockOutResultSeconds += 24 * 60 * 60; // 加24小时的秒数
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        long localTime = checkTime.toLocalTime().toSecondOfDay();
 | 
			
		||||
 | 
			
		||||
        return !(localTime > clockOutSeconds && localTime < clockOutResultSeconds);
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -217,18 +217,18 @@ public class BusLeaveServiceImpl extends ServiceImpl<BusLeaveMapper, BusLeave>
 | 
			
		||||
        Long projectId = oldLeave.getProjectId();
 | 
			
		||||
        // 遍历每一天
 | 
			
		||||
        List<BusAttendance> attendanceList = new ArrayList<>();
 | 
			
		||||
        for (long i = 0; i < day; i++) {
 | 
			
		||||
            BusAttendance attendance = new BusAttendance();
 | 
			
		||||
            attendance.setUserId(userId);
 | 
			
		||||
            attendance.setUserName(userName);
 | 
			
		||||
            attendance.setProjectId(projectId);
 | 
			
		||||
            Date date = DateUtils.addDays(startTime, (int) i);
 | 
			
		||||
            attendance.setLeaveId(id);
 | 
			
		||||
            attendance.setClockDate(date);
 | 
			
		||||
            attendance.setClockStatus(BusAttendanceClockStatusEnum.LEAVE.getValue());
 | 
			
		||||
            attendance.setCommuter(BusAttendanceCommuterEnum.ALLDAY.getValue());
 | 
			
		||||
            attendanceList.add(attendance);
 | 
			
		||||
        }
 | 
			
		||||
//        for (long i = 0; i < day; i++) {
 | 
			
		||||
//            BusAttendance attendance = new BusAttendance();
 | 
			
		||||
//            attendance.setUserId(userId);
 | 
			
		||||
//            attendance.setUserName(userName);
 | 
			
		||||
//            attendance.setProjectId(projectId);
 | 
			
		||||
//            Date date = DateUtils.addDays(startTime, (int) i);
 | 
			
		||||
//            attendance.setLeaveId(id);
 | 
			
		||||
//            attendance.setClockDate(date);
 | 
			
		||||
//            attendance.setClockStatus(BusAttendanceClockStatusEnum.LEAVE.getValue());
 | 
			
		||||
//            attendance.setCommuter(BusAttendanceCommuterEnum.ALLDAY.getValue());
 | 
			
		||||
//            attendanceList.add(attendance);
 | 
			
		||||
//        }
 | 
			
		||||
        boolean saveBatch = attendanceService.saveBatch(attendanceList);
 | 
			
		||||
        if (!saveBatch) {
 | 
			
		||||
            throw new ServiceException("更新考勤记录失败", HttpStatus.ERROR);
 | 
			
		||||
 | 
			
		||||
@ -99,59 +99,59 @@ public class BusReissueCardServiceImpl extends ServiceImpl<BusReissueCardMapper,
 | 
			
		||||
    @Override
 | 
			
		||||
    @Transactional(rollbackFor = Exception.class)
 | 
			
		||||
    public Boolean managerReview(BusReissueCardManagerReviewReq req) {
 | 
			
		||||
        Long id = req.getId();
 | 
			
		||||
        String managerOpinion = req.getManagerOpinion();
 | 
			
		||||
        // 判断该补卡记录是否存在
 | 
			
		||||
        BusReissueCard oldReissueCard = this.getById(id);
 | 
			
		||||
        if (oldReissueCard == null) {
 | 
			
		||||
            throw new ServiceException("施工人员补卡申请不存在", HttpStatus.NOT_FOUND);
 | 
			
		||||
        }
 | 
			
		||||
        // 如果已经审核过,则返回
 | 
			
		||||
        if (!BusOpinionStatusEnum.UNREAD.getValue().equals(managerOpinion)) {
 | 
			
		||||
            throw new ServiceException("该请假已审核,请勿重复操作", HttpStatus.BAD_REQUEST);
 | 
			
		||||
        }
 | 
			
		||||
        // 判断班组长是否审核通过
 | 
			
		||||
        String gangerOpinion = oldReissueCard.getGangerOpinion();
 | 
			
		||||
        if (!BusOpinionStatusEnum.PASS.getValue().equals(gangerOpinion)) {
 | 
			
		||||
            throw new ServiceException("请等待班组长审核通过后再进行操作", HttpStatus.BAD_REQUEST);
 | 
			
		||||
        }
 | 
			
		||||
        // todo 判断当前用户是否为项目管理员
 | 
			
		||||
        // 填充默认值,更新数据
 | 
			
		||||
        BusReissueCard reissueCard = new BusReissueCard();
 | 
			
		||||
        reissueCard.setId(id);
 | 
			
		||||
        reissueCard.setManagerOpinion(managerOpinion);
 | 
			
		||||
        reissueCard.setManagerExplain(req.getManagerExplain());
 | 
			
		||||
        reissueCard.setManagerTime(new Date());
 | 
			
		||||
        reissueCard.setRemark(req.getRemark());
 | 
			
		||||
        boolean result = this.updateById(reissueCard);
 | 
			
		||||
        if (!result) {
 | 
			
		||||
            throw new ServiceException("更新管理员审核操作失败", HttpStatus.ERROR);
 | 
			
		||||
        }
 | 
			
		||||
        // 更新考勤表记录
 | 
			
		||||
        BusAttendance oldAttendance = attendanceService.getById(oldReissueCard.getAttendanceId());
 | 
			
		||||
        if (oldAttendance == null) {
 | 
			
		||||
            throw new ServiceException("考勤记录不存在", HttpStatus.NOT_FOUND);
 | 
			
		||||
        }
 | 
			
		||||
        BusAttendance attendance = new BusAttendance();
 | 
			
		||||
        BusProject project = projectService.getById(oldReissueCard.getProjectId());
 | 
			
		||||
        // 根据补卡类型更新考勤时间
 | 
			
		||||
        String[] clockTime = project.getPunchRange().split(",");
 | 
			
		||||
        String reissueCardType = oldReissueCard.getReissueCardType();
 | 
			
		||||
        if (BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(reissueCardType)) {
 | 
			
		||||
            // 拼接时间,获取项目的上班打卡时间
 | 
			
		||||
            Date date = DateUtils.combineDateAndTime(oldAttendance.getClockDate(), clockTime[0] + ":00");
 | 
			
		||||
            attendance.setClockTime(date);
 | 
			
		||||
        } else if (BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(reissueCardType)) {
 | 
			
		||||
            // 拼接时间,获取项目的下班打卡时间
 | 
			
		||||
            Date date = DateUtils.combineDateAndTime(oldAttendance.getClockDate(), clockTime[1] + ":00");
 | 
			
		||||
            attendance.setClockTime(date);
 | 
			
		||||
        }
 | 
			
		||||
        attendance.setId(oldReissueCard.getAttendanceId());
 | 
			
		||||
        attendance.setClockStatus(BusAttendanceClockStatusEnum.REISSUE.getValue());
 | 
			
		||||
        boolean updateAttendance = attendanceService.updateById(attendance);
 | 
			
		||||
        if (!updateAttendance) {
 | 
			
		||||
            throw new ServiceException("更新考勤记录失败", HttpStatus.ERROR);
 | 
			
		||||
        }
 | 
			
		||||
//        Long id = req.getId();
 | 
			
		||||
//        String managerOpinion = req.getManagerOpinion();
 | 
			
		||||
//        // 判断该补卡记录是否存在
 | 
			
		||||
//        BusReissueCard oldReissueCard = this.getById(id);
 | 
			
		||||
//        if (oldReissueCard == null) {
 | 
			
		||||
//            throw new ServiceException("施工人员补卡申请不存在", HttpStatus.NOT_FOUND);
 | 
			
		||||
//        }
 | 
			
		||||
//        // 如果已经审核过,则返回
 | 
			
		||||
//        if (!BusOpinionStatusEnum.UNREAD.getValue().equals(managerOpinion)) {
 | 
			
		||||
//            throw new ServiceException("该请假已审核,请勿重复操作", HttpStatus.BAD_REQUEST);
 | 
			
		||||
//        }
 | 
			
		||||
//        // 判断班组长是否审核通过
 | 
			
		||||
//        String gangerOpinion = oldReissueCard.getGangerOpinion();
 | 
			
		||||
//        if (!BusOpinionStatusEnum.PASS.getValue().equals(gangerOpinion)) {
 | 
			
		||||
//            throw new ServiceException("请等待班组长审核通过后再进行操作", HttpStatus.BAD_REQUEST);
 | 
			
		||||
//        }
 | 
			
		||||
//        // todo 判断当前用户是否为项目管理员
 | 
			
		||||
//        // 填充默认值,更新数据
 | 
			
		||||
//        BusReissueCard reissueCard = new BusReissueCard();
 | 
			
		||||
//        reissueCard.setId(id);
 | 
			
		||||
//        reissueCard.setManagerOpinion(managerOpinion);
 | 
			
		||||
//        reissueCard.setManagerExplain(req.getManagerExplain());
 | 
			
		||||
//        reissueCard.setManagerTime(new Date());
 | 
			
		||||
//        reissueCard.setRemark(req.getRemark());
 | 
			
		||||
//        boolean result = this.updateById(reissueCard);
 | 
			
		||||
//        if (!result) {
 | 
			
		||||
//            throw new ServiceException("更新管理员审核操作失败", HttpStatus.ERROR);
 | 
			
		||||
//        }
 | 
			
		||||
//        // 更新考勤表记录
 | 
			
		||||
//        BusAttendance oldAttendance = attendanceService.getById(oldReissueCard.getAttendanceId());
 | 
			
		||||
//        if (oldAttendance == null) {
 | 
			
		||||
//            throw new ServiceException("考勤记录不存在", HttpStatus.NOT_FOUND);
 | 
			
		||||
//        }
 | 
			
		||||
//        BusAttendance attendance = new BusAttendance();
 | 
			
		||||
//        BusProject project = projectService.getById(oldReissueCard.getProjectId());
 | 
			
		||||
//        // 根据补卡类型更新考勤时间
 | 
			
		||||
//        String[] clockTime = project.getPunchRange().split(",");
 | 
			
		||||
//        String reissueCardType = oldReissueCard.getReissueCardType();
 | 
			
		||||
//        if (BusAttendanceCommuterEnum.CLOCKIN.getValue().equals(reissueCardType)) {
 | 
			
		||||
//            // 拼接时间,获取项目的上班打卡时间
 | 
			
		||||
//            Date date = DateUtils.combineDateAndTime(oldAttendance.getClockDate(), clockTime[0] + ":00");
 | 
			
		||||
//            attendance.setClockTime(date);
 | 
			
		||||
//        } else if (BusAttendanceCommuterEnum.CLOCKOUT.getValue().equals(reissueCardType)) {
 | 
			
		||||
//            // 拼接时间,获取项目的下班打卡时间
 | 
			
		||||
//            Date date = DateUtils.combineDateAndTime(oldAttendance.getClockDate(), clockTime[1] + ":00");
 | 
			
		||||
//            attendance.setClockTime(date);
 | 
			
		||||
//        }
 | 
			
		||||
//        attendance.setId(oldReissueCard.getAttendanceId());
 | 
			
		||||
//        attendance.setClockStatus(BusAttendanceClockStatusEnum.REISSUE.getValue());
 | 
			
		||||
//        boolean updateAttendance = attendanceService.updateById(attendance);
 | 
			
		||||
//        if (!updateAttendance) {
 | 
			
		||||
//            throw new ServiceException("更新考勤记录失败", HttpStatus.ERROR);
 | 
			
		||||
//        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,7 @@
 | 
			
		||||
<?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="org.dromara.project.mapper.BusAttendanceRuleMapper">
 | 
			
		||||
 | 
			
		||||
</mapper>
 | 
			
		||||
		Reference in New Issue
	
	Block a user