diff --git a/file/resource/fonts/simhei.ttf b/file/resource/fonts/simhei.ttf new file mode 100644 index 00000000..5bd4687e Binary files /dev/null and b/file/resource/fonts/simhei.ttf differ diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/domain/SubConstructionUser.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/domain/SubConstructionUser.java index a743de32..8d28af2c 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/domain/SubConstructionUser.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/contractor/domain/SubConstructionUser.java @@ -196,7 +196,5 @@ public class SubConstructionUser extends BaseEntity { */ private String remark; - private LocalDate firstDate; - } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/app/BusAttendanceAppController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/app/BusAttendanceAppController.java index 28e40d4d..641ef4b4 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/app/BusAttendanceAppController.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/controller/app/BusAttendanceAppController.java @@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import jakarta.annotation.Resource; import jakarta.validation.constraints.NotNull; import org.dromara.common.core.domain.R; +import org.dromara.common.idempotent.annotation.RepeatSubmit; import org.dromara.common.mybatis.core.page.PageQuery; import org.dromara.common.mybatis.core.page.TableDataInfo; import org.dromara.common.satoken.utils.LoginHelper; @@ -31,8 +32,10 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.time.LocalDate; +import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.List; +import java.util.concurrent.TimeUnit; /** * app考勤 @@ -61,6 +64,7 @@ public class BusAttendanceAppController extends BaseController { /** * 人脸坐标打卡 */ + @RepeatSubmit(interval = 3, timeUnit = TimeUnit.SECONDS,message = "3分钟内禁止重复打卡") @PostMapping("/punch/card/face") public R punchCardByFace(@RequestPart("file") MultipartFile file, BusAttendancePunchCardByFaceReq req) { return R.ok(attendanceService.punchCardByFace(file, req)); @@ -183,8 +187,14 @@ public class BusAttendanceAppController extends BaseController { SubConstructionUser bySysUserId = constructionUserService.getBySysUserId(userId); if(bySysUserId == null || bySysUserId.getFirstDate() == null){ daysCountVo.setEntryDays(0); - }else{ - daysCountVo.setEntryDays(LocalDate.now().getDayOfYear() - bySysUserId.getFirstDate().getDayOfYear()); + } else { + LocalDate firstDate = bySysUserId.getFirstDate(); + if (firstDate.isAfter(LocalDate.now())) { + daysCountVo.setEntryDays(0); // 防止未来日期导致负数 + } else { + long daysDifference = ChronoUnit.DAYS.between(firstDate, LocalDate.now()); + daysCountVo.setEntryDays((int) daysDifference); + } } List list = attendanceService.list(Wrappers.lambdaQuery() .eq(BusAttendance::getUserId, userId) diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusUserProjectRelevancyService.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusUserProjectRelevancyService.java index f1cd33b4..08908f98 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusUserProjectRelevancyService.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/IBusUserProjectRelevancyService.java @@ -157,4 +157,8 @@ public interface IBusUserProjectRelevancyService extends IService getVoPage(Page userProjectRelevancyPage); + /** + * 判断用户是否是施工人员 + */ + Boolean isConstruct(Long userId,Long projectId); } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java index bba71736..4790ca5d 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusAttendanceServiceImpl.java @@ -403,14 +403,62 @@ public class BusAttendanceServiceImpl extends ServiceImpl punchRangeList = getPunchRangeByProjectIdAndUserId(req.getProjectId(), userId); + Long projectId = req.getProjectId(); + + //判断是否要求范围内打卡 + 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 isConstruct = "1".equals(relevancy.getUserType()); + List rangeIds = new ArrayList<>(); + if (isConstruct) { + 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()); + //需要考虑班组不设置考勤范围 + if("1".equals(team.getIsClockIn())){ + return true; + } + try { + JSONArray jsonArray = JSONUtil.parseArray(team.getPunchRange()); + rangeIds = jsonArray.toList(Long.class); + } catch (Exception e) { + + } + } + // 再获取项目的规则 + BusAttendanceRuleVo busAttendanceRuleVo = attendanceRuleService.queryByProjectId(projectId); + if(busAttendanceRuleVo != null && "2".equals(busAttendanceRuleVo.getType())){ + return true; + } + + List punchRangeList = projectPunchrangeService.lambdaQuery() + .in(CollectionUtil.isNotEmpty(rangeIds), BusProjectPunchrange::getId, rangeIds) + .eq(BusProjectPunchrange::getProjectId, projectId) + .list() + .stream() + .map(BusProjectPunchrange::getPunchRange) + .toList(); + if (CollUtil.isEmpty(punchRangeList)) { - throw new ServiceException("项目未配置考勤范围", HttpStatus.BAD_REQUEST); + throw new ServiceException(isConstruct?"班组":"项目"+"未配置考勤范围", HttpStatus.BAD_REQUEST); } List matchingRange = JSTUtil.findMatchingRange(req.getLat(), req.getLng(), punchRangeList); return matchingRange != null; } + @Override public List getTodayAttendance(Long projectId) { diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java index 08123af0..1170f48b 100644 --- a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/project/service/impl/BusProjectTeamMemberServiceImpl.java @@ -162,7 +162,7 @@ public class BusProjectTeamMemberServiceImpl extends ServiceImpl list = this.list(Wrappers.lambdaQuery(BusUserProjectRelevancy.class) + .eq(BusUserProjectRelevancy::getUserId, userId) + .eq(BusUserProjectRelevancy::getProjectId, projectId) + ); + + return list.stream().allMatch(relevancy -> "1".equals(relevancy.getUserType())); + } } diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/controller/TransferDataController.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/controller/TransferDataController.java new file mode 100644 index 00000000..6e7f902b --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/controller/TransferDataController.java @@ -0,0 +1,189 @@ +package org.dromara.transferData.controller; + +import cn.hutool.core.collection.CollectionUtil; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import jakarta.activation.MimetypesFileTypeMap; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.dromara.project.domain.vo.BusAttendanceRuleVo; +import org.dromara.transferData.domain.ConstructionUserCopy; +import org.dromara.transferData.domain.OldAttendance; +import org.dromara.transferData.mapper.TransferDataMapper; +import org.dromara.contractor.service.ISubConstructionUserService; +import org.dromara.project.domain.BusAttendance; +import org.dromara.project.service.IBusAttendanceRuleService; +import org.dromara.project.service.IBusAttendanceService; +import org.dromara.system.domain.vo.SysOssVo; +import org.dromara.system.service.ISysOssService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpResponse; +import java.net.http.HttpRequest; +import java.nio.file.Paths; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoUnit; +import java.util.List; + +@RestController +@RequestMapping("/transferData") +@Slf4j +public class TransferDataController { + + @Resource + private TransferDataMapper transferDataMapper; + @Resource + private IBusAttendanceService attendanceService; + @Resource + private IBusAttendanceRuleService attendanceRuleService; + @Resource + private ISubConstructionUserService constructionUserService; + @Resource + private ISysOssService ossService; + + // 两个候选基础URL + private static final String[] BASE_URLS = { + "http://xny.yj-3d.com:7464", + "http://xny.yj-3d.com:7363" + }; + + + @RequestMapping("/transferAttendance") + public void transferAttendance() { + List data = transferDataMapper.getData(); + for (OldAttendance oldAttendance : data) { + ConstructionUserCopy constructionUserCopy = transferDataMapper.getConstructionUserCopy(oldAttendance.getOpenid()); + + if (constructionUserCopy == null) { + continue; + } + LocalDate clockDate = LocalDate.parse(oldAttendance.getPrintingDate()); + //判定是否重读 + List list = attendanceService.list(Wrappers.lambdaQuery(BusAttendance.class) + .eq(BusAttendance::getUserId, constructionUserCopy.getSysUserId()) + .eq(BusAttendance::getProjectId, constructionUserCopy.getProjectId()) + .eq(BusAttendance::getClockDate, clockDate) + ); + if(CollectionUtil.isNotEmpty(list)){ + continue; + } + + BusAttendance busAttendance = new BusAttendance(); + + if(oldAttendance.getPacePhoto()!=null){ + Long l = handleFaceImage(oldAttendance.getPacePhoto()); + busAttendance.setFacePic(l==null?"":l.toString()); + } + + busAttendance.setUserName(constructionUserCopy.getUserName()); + busAttendance.setUserId(constructionUserCopy.getSysUserId()); + busAttendance.setProjectId(constructionUserCopy.getProjectId()); + + // 转换日期字段 + busAttendance.setClockDate(clockDate); + + //状态 + busAttendance.setClockStatus(oldAttendance.getIsPinch()); + //类型 + busAttendance.setClockType(oldAttendance.getCommuter()); + //位置 + busAttendance.setClockLocation(oldAttendance.getLocation()); + busAttendance.setLng(oldAttendance.getLng()); + busAttendance.setLat(oldAttendance.getLat()); + //打卡时间 + if (!"4".equals(oldAttendance.getIsPinch())) { + busAttendance.setClockTime(parseClockOn(oldAttendance.getClockOn(), busAttendance)); + } + //规则和迟到早退时间计算 + BusAttendanceRuleVo busAttendanceRuleVo = attendanceRuleService.queryByProjectId(busAttendance.getProjectId()); + if (busAttendanceRuleVo != null) { + LocalTime clockInTime = busAttendanceRuleVo.getClockInTime(); + LocalTime clockOutTime = busAttendanceRuleVo.getClockOutTime(); + if("1".equals(busAttendance.getClockType())){ + busAttendance.setRuleTime(clockInTime); + if("2".equals(oldAttendance.getIsPinch())){ + LocalDateTime ruleDateTime = LocalDateTime.of(busAttendance.getClockDate(), clockInTime); + long minutesDiff = ChronoUnit.MINUTES.between(ruleDateTime, busAttendance.getClockTime()); + long absMinutes = Math.abs(minutesDiff); + busAttendance.setMinuteCount((int)absMinutes); + } + }else { + busAttendance.setRuleTime(clockOutTime); + if("3".equals(oldAttendance.getIsPinch())){ + LocalDateTime ruleDateTime = LocalDateTime.of(busAttendance.getClockDate(), clockOutTime); + long minutesDiff = ChronoUnit.MINUTES.between(ruleDateTime, busAttendance.getClockTime()); + long absMinutes = Math.abs(minutesDiff); + busAttendance.setMinuteCount((int)absMinutes); + } + } + busAttendance.setHandle("5".equals(oldAttendance.getIsPinch())?"1":"0"); + } + + } + + + } + + + public Long handleFaceImage(String relativePath) { + String normalizedPath = relativePath.replace("\\", "/"); + String filename = Paths.get(normalizedPath).getFileName().toString(); + + // 使用 MimetypesFileTypeMap 解析 + MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap(); + String contentType = fileTypeMap.getContentType(filename); + + for (String baseUrl : BASE_URLS) { + String fullUrl = baseUrl + normalizedPath; + try { + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(fullUrl)) + .GET() + .build(); + + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + + if (response.statusCode() == 200) { + long contentLength = response.headers().firstValueAsLong("Content-Length").orElse(-1); + SysOssVo ossVo = ossService.upload(response.body(), filename, contentType, contentLength); + return ossVo.getOssId(); + } + } catch (Exception e) { + log.warn("尝试URL失败: {}", fullUrl, e); + continue; + } + } + return null; + } + + private LocalDateTime parseClockOn(String clockOn, BusAttendance busAttendance) { + if (clockOn == null || "缺卡".equals(clockOn)) { + return null; + } + + try { + // 1. 尝试完整日期时间解析(如 "2023-10-14 07:35:07") + return LocalDateTime.parse(clockOn); + } catch (DateTimeParseException e) { + try { + // 2. 仅时间解析(如 "5:38:00")并结合已有的日期 + LocalDate date = busAttendance.getClockDate(); + if (date == null) { + return null; // 日期不存在时返回 null + } + LocalTime time = LocalTime.parse(clockOn); + return LocalDateTime.of(date, time); + } catch (DateTimeParseException ex) { + log.warn("无法解析打卡时间: {}", clockOn); + return null; + } + } + } +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/domain/ConstructionUserCopy.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/domain/ConstructionUserCopy.java new file mode 100644 index 00000000..3df86490 --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/domain/ConstructionUserCopy.java @@ -0,0 +1,14 @@ +package org.dromara.transferData.domain; + +import lombok.Data; + +@Data +public class ConstructionUserCopy { + private Long id; + + private Long sysUserId; + + private Long projectId; + + private String userName; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/domain/OldAttendance.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/domain/OldAttendance.java new file mode 100644 index 00000000..06a0d96f --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/domain/OldAttendance.java @@ -0,0 +1,131 @@ +package org.dromara.transferData.domain; + +import lombok.Data; +import java.math.BigDecimal; +import java.time.LocalDateTime; + +/** + * 打卡记录实体类 + */ +@Data +public class OldAttendance { + /** + * 主键ID + */ + private Long id; + + /** + * 人员姓名 + */ + private String userName; + + /** + * 人脸照 + */ + private String pacePhoto; + + /** + * 项目id + */ + private Long projectId; + + /** + * 创建者 + */ + private String createBy; + + /** + * 更新者 + */ + private String updateBy; + + /** + * 创建时间 + */ + private LocalDateTime createdAt; + + /** + * 更新时间 + */ + private LocalDateTime updatedAt; + + /** + * 删除时间 + */ + private LocalDateTime deletedAt; + + /** + * 上午打卡 + */ + private String clockOn; + + /** + * 下午打卡 + */ + private String clockOff; + + /** + * 年月日打卡时间 + */ + private String printingDate; + + /** + * 打卡状态:1正常,2迟到,3早退,4缺勤,5补卡 + */ + private String isPinch; + + /** + * 微信id + */ + private String openid; + + /** + * 代打id + */ + private String pinchOpenId; + + /** + * 多次打卡时间记录 + */ + private String clockRecord; + + /** + * 代打人姓名 + */ + private String pinchUserName; + + /** + * 上下班(1上班2下班) + */ + private String commuter; + + /** + * 打卡范围 + */ + private String punchRange; + + /** + * 日薪 + */ + private BigDecimal dailyWage; + + /** + * 经度 + */ + private String lng; + + /** + * 纬度 + */ + private String lat; + + /** + * 逆编码地址信息 + */ + private String location; + + /** + * 缺卡统一处理时间 + */ + private LocalDateTime missing; +} diff --git a/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/mapper/TransferDataMapper.java b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/mapper/TransferDataMapper.java new file mode 100644 index 00000000..3c14f34b --- /dev/null +++ b/xinnengyuan/ruoyi-modules/ruoyi-system/src/main/java/org/dromara/transferData/mapper/TransferDataMapper.java @@ -0,0 +1,24 @@ +package org.dromara.transferData.mapper; + +import com.baomidou.dynamic.datasource.annotation.DS; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; +import org.apache.ibatis.annotations.Select; +import org.dromara.transferData.domain.ConstructionUserCopy; +import org.dromara.transferData.domain.OldAttendance; + +import java.util.List; + + +@Mapper +public interface TransferDataMapper { + + + @DS("slave") + @Select("select * from bus_attendance") + List getData(); + + + @Select("select id,sys_user_id,project_id,user_name from sub_construction_user_copy1 where go_openid = #{openId}") + ConstructionUserCopy getConstructionUserCopy(@Param("openId") String openId); +}