src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java
@@ -2,44 +2,42 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.ruoyi.framework.web.domain.AjaxResult; import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto; import com.ruoyi.staff.pojo.HolidayApplication; import com.ruoyi.staff.pojo.PersonalAttendanceRecords; import com.ruoyi.staff.service.HolidayApplicationService; import com.ruoyi.staff.service.PersonalAttendanceRecordsService; import lombok.AllArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @AllArgsConstructor /** * <p> * 前端控制器 * </p> * * @author 芯导软件(江苏)有限公司 * @since 2026-02-09 01:20:07 */ @RestController @RequestMapping("/staff/personalAttendanceRecords") @RequestMapping("/personalAttendanceRecords") public class PersonalAttendanceRecordsController { @Autowired private PersonalAttendanceRecordsService personalAttendanceRecordsService; /** * 个人考勤记录分页查询 */ // 新增 @PostMapping("") public AjaxResult add(@RequestBody PersonalAttendanceRecords personalAttendanceRecord){ return AjaxResult.success(personalAttendanceRecordsService.add(personalAttendanceRecord)); } // 列表查询 @GetMapping("/listPage") public AjaxResult personalAttendanceRecordsListPage(Page page, PersonalAttendanceRecords personalAttendanceRecords) { return AjaxResult.success(personalAttendanceRecordsService.listPage(page, personalAttendanceRecords)); public AjaxResult listPage(Page page, PersonalAttendanceRecordsDto personalAttendanceRecordsDto){ return AjaxResult.success(personalAttendanceRecordsService.listPage(page, personalAttendanceRecordsDto)); } /** * 新增个人考勤记录 */ @PostMapping("/add") public AjaxResult add(@RequestBody PersonalAttendanceRecords personalAttendanceRecords) { return AjaxResult.success(personalAttendanceRecordsService.save(personalAttendanceRecords)); } /** * 修改个人考勤记录 */ @PutMapping("/update") public AjaxResult update(@RequestBody PersonalAttendanceRecords personalAttendanceRecords) { return AjaxResult.success(personalAttendanceRecordsService.updateById(personalAttendanceRecords)); } /** * 删除个人考勤记录 */ @DeleteMapping("/delete/{id}") public AjaxResult delete(@PathVariable("id") Long id) { return AjaxResult.success(personalAttendanceRecordsService.removeById(id)); @GetMapping("/today") public AjaxResult todayInfo(PersonalAttendanceRecordsDto personalAttendanceRecordsDto){ return AjaxResult.success(personalAttendanceRecordsService.todayInfo(personalAttendanceRecordsDto)); } } src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceRecords.java
@@ -1,52 +1,76 @@ package com.ruoyi.staff.pojo; import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import com.fasterxml.jackson.annotation.JsonFormat; import com.ruoyi.framework.aspectj.lang.annotation.Excel; import lombok.Data; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Getter; import lombok.Setter; import org.springframework.format.annotation.DateTimeFormat; import java.io.Serializable; import java.time.LocalDate; import java.time.LocalTime; @Data /** * <p> * * </p> * * @author 芯导软件(江苏)有限公司 * @since 2026-02-09 01:20:07 */ @Getter @Setter @TableName("personal_attendance_records") @ApiModel(value = "PersonalAttendanceRecords对象", description = "") public class PersonalAttendanceRecords implements Serializable { /** * 序号 */ @TableId(type = IdType.AUTO) private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.AUTO) private Long id; /** * 日期 */ @ApiModelProperty("员工在职id") private Long staffOnJobId; @ApiModelProperty("日期") @JsonFormat(pattern = "yyyy-MM-dd") @DateTimeFormat(pattern = "yyyy-MM-dd") private LocalDate date; /** * 签到时间 */ @ApiModelProperty("工作开始时间") @JsonFormat(pattern = "HH:mm") @DateTimeFormat(pattern = "HH:mm") private LocalTime checkIn; /** * 签退时间 */ private LocalDateTime workStartAt; @ApiModelProperty("工作结束时间") @JsonFormat(pattern = "HH:mm") @DateTimeFormat(pattern = "HH:mm") private LocalTime checkOut; /** * 工作时长 */ private String workHours; /** * 状态 */ private String status; /** * 租户ID */ private LocalDateTime workEndAt; @ApiModelProperty("工作时长") private BigDecimal workHours; @ApiModelProperty("状态 0正常 1迟到 2早退") private Byte status; @ApiModelProperty("备注") private String remark; @ApiModelProperty("租户id") @TableField(fill = FieldFill.INSERT) private Long tenantId; @ApiModelProperty("录入时间") @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @ApiModelProperty("更新时间") @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; } src/main/java/com/ruoyi/staff/service/PersonalAttendanceRecordsService.java
@@ -2,9 +2,24 @@ import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto; import com.ruoyi.staff.dto.StaffOnJobDto; import com.ruoyi.staff.pojo.PersonalAttendanceRecords; import com.baomidou.mybatisplus.extension.service.IService; import com.ruoyi.staff.pojo.StaffOnJob; /** * <p> * 服务类 * </p> * * @author 芯导软件(江苏)有限公司 * @since 2026-02-09 01:20:07 */ public interface PersonalAttendanceRecordsService extends IService<PersonalAttendanceRecords> { IPage listPage(Page page, PersonalAttendanceRecords personalAttendanceRecords); IPage<PersonalAttendanceRecordsDto> listPage(Page page, PersonalAttendanceRecordsDto personalAttendanceRecordsDto); int add(PersonalAttendanceRecords personalAttendanceRecords); PersonalAttendanceRecordsDto todayInfo(PersonalAttendanceRecordsDto personalAttendanceRecordsDto); } src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java
@@ -3,21 +3,195 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper; import com.ruoyi.common.exception.base.BaseException; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.project.system.domain.SysDept; import com.ruoyi.project.system.mapper.SysDeptMapper; import com.ruoyi.project.system.service.ISysDictDataService; import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto; import com.ruoyi.staff.mapper.StaffOnJobMapper; import com.ruoyi.staff.pojo.PersonalAttendanceRecords; import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper; import com.ruoyi.staff.pojo.StaffOnJob; import com.ruoyi.staff.service.PersonalAttendanceRecordsService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; /** * <p> * 服务实现类 * </p> * * @author 芯导软件(江苏)有限公司 * @since 2026-02-09 01:20:07 */ @Service public class PersonalAttendanceRecordsServiceImpl extends ServiceImpl<PersonalAttendanceRecordsMapper, PersonalAttendanceRecords> implements PersonalAttendanceRecordsService { @Autowired private PersonalAttendanceRecordsMapper personalAttendanceRecordsMapper; @Autowired private StaffOnJobMapper staffOnJobMapper; @Autowired private ISysDictDataService dictDataService; @Autowired private SysDeptMapper sysDeptMapper; @Override public IPage listPage(Page page, PersonalAttendanceRecords personalAttendanceRecords) { // return personalAttendanceRecordsMapper.ListPage(page, personalAttendanceRecords); return baseMapper.selectPage(page, new QueryWrapper<>(personalAttendanceRecords)); @Transactional(rollbackFor = Exception.class) public int add(PersonalAttendanceRecords personalAttendanceRecords) { // 当前时间 LocalDate currentDate = LocalDate.now(); // 首先根据用户ID查询员工信息 QueryWrapper<StaffOnJob> staffQueryWrapper = new QueryWrapper<>(); staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername()); StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper); if (staffOnJob == null) { throw new BaseException("当前用户没有对应的员工信息"); } // 根据员工ID和当前日期查询打卡记录 QueryWrapper<PersonalAttendanceRecords> attendanceQueryWrapper = new QueryWrapper<>(); attendanceQueryWrapper.eq("staff_on_job_id", staffOnJob.getId()) .eq("date", currentDate); PersonalAttendanceRecords attendanceRecord = personalAttendanceRecordsMapper.selectOne(attendanceQueryWrapper); DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm"); // 根据字典设置的考勤时间判断迟到早退 if (attendanceRecord == null) { // 不存在打卡记录,创建新记录 personalAttendanceRecords.setStaffOnJobId(staffOnJob.getId()); personalAttendanceRecords.setDate(currentDate); personalAttendanceRecords.setWorkStartAt(LocalDateTime.now()); personalAttendanceRecords.setStatus(determineAttendanceStatus(personalAttendanceRecords.getWorkStartAt(), true)); personalAttendanceRecords.setRemark(personalAttendanceRecords.getRemark()); personalAttendanceRecords.setTenantId(staffOnJob.getTenantId()); return personalAttendanceRecordsMapper.insert(personalAttendanceRecords); } else { if (attendanceRecord.getWorkEndAt() == null) { // 更新工作结束时间和工作时长 attendanceRecord.setWorkEndAt(LocalDateTime.now()); // 计算工作时长(精确到分钟,保留2位小数) LocalDateTime startTime = attendanceRecord.getWorkStartAt(); LocalDateTime endTime = attendanceRecord.getWorkEndAt(); // 计算两个时间之间的分钟数 long totalMinutes = java.time.Duration.between(startTime, endTime).toMinutes(); BigDecimal workHours = BigDecimal.valueOf(totalMinutes) .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP); attendanceRecord.setWorkHours(workHours); // 更新考勤状态 attendanceRecord.setStatus(determineAttendanceStatus(attendanceRecord.getWorkEndAt(), false)); return personalAttendanceRecordsMapper.updateById(attendanceRecord); } else { throw new BaseException("您已经打过卡了,无需重复打卡!!!"); } } } // 根据实际时间和是否上班时间判断考勤状态 // 0 正常 1 迟到 2 早退 private byte determineAttendanceStatus(LocalDateTime actualTime, boolean isStart) { try { // 获取考勤时间配置 String dictType = "sys_work_time"; // 考勤时间字典类型 String timeConfig; if (isStart) { // 上班时间配置,默认为09:00 timeConfig = dictDataService.selectDictLabel(dictType, "work_start_time"); if (timeConfig == null || timeConfig.trim().isEmpty()) { timeConfig = "09:00"; } } else { // 下班时间配置,默认为18:00 timeConfig = dictDataService.selectDictLabel(dictType, "work_end_time"); if (timeConfig == null || timeConfig.trim().isEmpty()) { timeConfig = "18:00"; } } // 解析标准时间 String[] timeParts = timeConfig.split(":"); int standardHour = Integer.parseInt(timeParts[0]); int standardMinute = Integer.parseInt(timeParts[1]); // 获取实际时间的时分 int actualHour = actualTime.getHour(); int actualMinute = actualTime.getMinute(); // 判断状态 if (isStart) { // 上班打卡:超过标准时间算迟到 if (actualHour > standardHour || (actualHour == standardHour && actualMinute > standardMinute)) { return 1; // 迟到 } } else { // 下班打卡:早于标准时间算早退 if (actualHour < standardHour || (actualHour == standardHour && actualMinute < standardMinute)) { return 2; // 早退 } } return 0; // 正常 } catch (Exception e) { // 如果获取配置失败,默认返回正常状态 log.warn("获取考勤时间配置失败,使用默认状态:" + e.getMessage()); return 0; } } @Override public IPage<PersonalAttendanceRecordsDto> listPage(Page page, PersonalAttendanceRecordsDto personalAttendanceRecordsDto) { return personalAttendanceRecordsMapper.listPage(page,personalAttendanceRecordsDto); } @Override public PersonalAttendanceRecordsDto todayInfo(PersonalAttendanceRecordsDto personalAttendanceRecordsDto) { // 获取当前日期 LocalDate currentDate = LocalDate.now(); // 首先根据用户ID查询员工信息 QueryWrapper<StaffOnJob> staffQueryWrapper = new QueryWrapper<>(); staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername()); StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper); if (staffOnJob == null) { return null; // 当前用户没有对应的员工信息 } // 根据员工ID和当前日期查询打卡记录 QueryWrapper<PersonalAttendanceRecords> attendanceQueryWrapper = new QueryWrapper<>(); attendanceQueryWrapper.eq("staff_on_job_id", staffOnJob.getId()) .eq("date", currentDate); PersonalAttendanceRecords attendanceRecord = personalAttendanceRecordsMapper.selectOne(attendanceQueryWrapper); // 返回参数 PersonalAttendanceRecordsDto resultDto = new PersonalAttendanceRecordsDto(); if (attendanceRecord != null) { // 如果有打卡记录,复制打卡记录信息 BeanUtils.copyProperties(attendanceRecord, resultDto); } // 员工相关信息 resultDto.setStaffName(staffOnJob.getStaffName()); resultDto.setStaffNo(staffOnJob.getStaffNo()); resultDto.setDeptId(staffOnJob.getSysDeptId() != null ? staffOnJob.getSysDeptId() : null); SysDept dept = sysDeptMapper.selectDeptById(staffOnJob.getSysDeptId()); resultDto.setDeptName(dept != null ? dept.getDeptName() : null); return resultDto; } } src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml
@@ -1,7 +1,39 @@ <?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" > <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper"> <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="com.ruoyi.staff.pojo.PersonalAttendanceRecords"> <id column="id" property="id" /> <result column="staff_on_job_id" property="staffOnJobId" /> <result column="date" property="date" /> <result column="work_start_at" property="workStartAt" /> <result column="work_end_at" property="workEndAt" /> <result column="work_hours" property="workHours" /> <result column="status" property="status" /> <result column="remark" property="remark" /> <result column="tenant_id" property="tenantId" /> <result column="create_time" property="createTime" /> <result column="update_time" property="updateTime" /> </resultMap> </mapper> <select id="listPage" resultType="com.ruoyi.staff.dto.PersonalAttendanceRecordsDto"> SELECT personal_attendance_records.*, soj.staff_name as staffName, soj.staff_no as staffNo, sd.dept_name as deptName FROM personal_attendance_records LEFT JOIN staff_on_job soj ON soj.id = personal_attendance_records.staff_on_job_id LEFT JOIN sys_dept sd ON sd.dept_id = soj.sys_dept_id where 1=1 <if test="params.deptId != null and params.deptId > 0"> AND sys_dept.dept_id = #{params.deptId} </if> <if test="params.date != null"> AND personal_attendance_records.date = DATE_FORMAT(#{params.date},'%Y-%m-%d') </if> </select> </mapper>