liyong
2026-02-26 2ae58a2f2d53c220c4b02d8e9f17770f63397b1e
人力资源模块迁移
已添加13个文件
已修改22个文件
1167 ■■■■■ 文件已修改
src/main/java/com/ruoyi/staff/controller/AnalyticsController.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/HolidayApplicationController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffContractController.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/StaffSchedulingController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/SaveStaffSchedulingDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/StaffLeaveDto.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/StaffSchedulingDto.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceLocationConfigMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceRecordsMapper.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/StaffContractMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/StaffLeaveMapper.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/HolidayApplication.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceLocationConfig.java 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceRecords.java 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffContract.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffLeave.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffOnJob.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/AnalyticsService.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/PersonalAttendanceLocationConfigService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/PersonalAttendanceRecordsService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/AnalyticsServiceImpl.java 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceLocationConfigServiceImpl.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java 241 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/task/PersonalAttendanceRecordsTask.java 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/utils/LocationUtils.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/vo/MonthlyTurnoverRateVo.java 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/vo/TotalTurnoverRateVo.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductOrderMapper.xml 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/AnalyticsController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package com.ruoyi.staff.controller;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.staff.service.AnalyticsService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/staff/analytics")
public class AnalyticsController {
    @Resource
    private AnalyticsService analyticsService;
    @GetMapping("/reason")
    public AjaxResult staffLeaveReasonAnalytics() {
        return AjaxResult.success(analyticsService.staffLeaveReasonAnalytics());
    }
    @GetMapping("/monthly_turnover_rate")
    public AjaxResult getMonthlyTurnoverRateFor12Months() {
        return AjaxResult.success(analyticsService.getMonthlyTurnoverRateFor12Months());
    }
    @GetMapping("/total_statistic")
    public AjaxResult getTotalStatistic() {
        return AjaxResult.success(analyticsService.getTotalStatistic());
    }
}
src/main/java/com/ruoyi/staff/controller/HolidayApplicationController.java
@@ -3,7 +3,6 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.staff.pojo.HolidayApplication;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.service.HolidayApplicationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,49 @@
package com.ruoyi.staff.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.service.PersonalAttendanceLocationConfigService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
 * <p>
 * äººå‘˜æ‰“卡规则配置 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-11 09:41:34
 */
@RestController
@RequestMapping("/personalAttendanceLocationConfig")
@Api(tags = "人员打卡规则配置")
public class PersonalAttendanceLocationConfigController {
    @Autowired
    private PersonalAttendanceLocationConfigService personalAttendanceLocationConfigService;
    @ApiOperation("新增/修改人员打卡规则配置")
    @PostMapping("/add")
    public R add(@RequestBody PersonalAttendanceLocationConfig personalAttendanceLocationConfig){
        return R.ok(personalAttendanceLocationConfigService.saveOrUpdate(personalAttendanceLocationConfig));
    }
    @ApiOperation("分页查询人员打卡规则配置")
    @GetMapping("/listPage")
    public R listPage(Page page){
        return R.ok(personalAttendanceLocationConfigService.page(page));
    }
    @ApiOperation("删除人员打卡规则配置")
    @DeleteMapping("/del")
    public R del(@RequestBody List<Integer> ids) {
        return R.ok(personalAttendanceLocationConfigService.removeBatchByIds(ids));
    }
}
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java
@@ -2,44 +2,52 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto;
import com.ruoyi.staff.service.PersonalAttendanceRecordsService;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
@AllArgsConstructor
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
/**
 * <p>
 *  å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-09 01:20:07
 */
@RestController
@RequestMapping("/staff/personalAttendanceRecords")
@RequestMapping("/personalAttendanceRecords")
@Api(tags = "人员打卡签到")
public class PersonalAttendanceRecordsController {
    @Autowired
    @Resource
    private PersonalAttendanceRecordsService personalAttendanceRecordsService;
    /**
     * ä¸ªäººè€ƒå‹¤è®°å½•分页查询
     */
    @ApiOperation("新增打卡签到")
    @PostMapping("")
    public AjaxResult add(@RequestBody PersonalAttendanceRecordsDto personalAttendanceRecordsDto){
        return AjaxResult.success(personalAttendanceRecordsService.add(personalAttendanceRecordsDto));
    }
    @ApiOperation("分页查询打卡签到")
    @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));
    @ApiOperation("获取当前人的考勤相关数据")
    @GetMapping("/today")
    public AjaxResult todayInfo(PersonalAttendanceRecordsDto personalAttendanceRecordsDto){
        return AjaxResult.success(personalAttendanceRecordsService.todayInfo(personalAttendanceRecordsDto));
    }
    /**
     * ä¿®æ”¹ä¸ªäººè€ƒå‹¤è®°å½•
     */
    @PutMapping("/update")
    public AjaxResult update(@RequestBody PersonalAttendanceRecords personalAttendanceRecords) {
        return AjaxResult.success(personalAttendanceRecordsService.updateById(personalAttendanceRecords));
    @ApiOperation("导出打卡签到")
    @PostMapping("/export")
    public void export(HttpServletResponse response, PersonalAttendanceRecordsDto personalAttendanceRecordsDto) {
        personalAttendanceRecordsService.export(response, personalAttendanceRecordsDto);
    }
    /**
     * åˆ é™¤ä¸ªäººè€ƒå‹¤è®°å½•
     */
    @DeleteMapping("/delete/{id}")
    public AjaxResult delete(@PathVariable("id") Long id) {
        return AjaxResult.success(personalAttendanceRecordsService.removeById(id));
    }
}
src/main/java/com/ruoyi/staff/controller/StaffContractController.java
@@ -5,7 +5,9 @@
import com.ruoyi.staff.pojo.StaffContract;
import com.ruoyi.staff.service.StaffContractService;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
src/main/java/com/ruoyi/staff/controller/StaffOnJobController.java
@@ -16,7 +16,6 @@
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;
/**
src/main/java/com/ruoyi/staff/controller/StaffSchedulingController.java
@@ -2,7 +2,6 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.compensationperformance.pojo.CompensationPerformance;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package com.ruoyi.staff.dto;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalTime;
@Data
@ExcelIgnoreUnannotated
public class PersonalAttendanceRecordsDto extends PersonalAttendanceRecords {
    @Excel(name = "姓名", sort = 3)
    private String staffName;
    @Excel(name = "工号", sort = 4)
    private String staffNo;
    @Excel(name = "部门", sort = 2)
    private String deptName;
    private Long deptId;
    //打卡的经度
    private Double longitude;
    //打卡的纬度
    private Double latitude;
    //标准上班时间
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime startAt;
    //标准下班时间
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime  endAt;
}
src/main/java/com/ruoyi/staff/dto/SaveStaffSchedulingDto.java
@@ -1,12 +1,10 @@
package com.ruoyi.staff.dto;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
src/main/java/com/ruoyi/staff/dto/StaffLeaveDto.java
@@ -1,11 +1,8 @@
package com.ruoyi.staff.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import com.ruoyi.staff.pojo.StaffLeave;
import lombok.Data;
import java.util.Date;
@Data
public class StaffLeaveDto extends StaffLeave {
@@ -95,4 +92,11 @@
     */
    @Excel(name = "紧急联系人电话", sort = 15)
    private String emergencyContactPhone;
    private int count;
    /**
     * ç¦»èŒåŽŸå› æ–‡æœ¬
     */
    private String reasonText;
}
src/main/java/com/ruoyi/staff/dto/StaffSchedulingDto.java
@@ -1,7 +1,6 @@
package com.ruoyi.staff.dto;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceLocationConfigMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.staff.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * äººå‘˜æ‰“卡规则配置 Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-11 09:41:34
 */
@Mapper
public interface PersonalAttendanceLocationConfigMapper extends BaseMapper<PersonalAttendanceLocationConfig> {
}
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceRecordsMapper.java
@@ -1,9 +1,31 @@
package com.ruoyi.staff.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.pojo.StaffOnJob;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
 * <p>
 *  Mapper æŽ¥å£
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-09 01:20:07
 */
@Mapper
public interface PersonalAttendanceRecordsMapper extends BaseMapper<PersonalAttendanceRecords> {
    IPage<PersonalAttendanceRecordsDto> listPage(Page page, @Param("params") PersonalAttendanceRecordsDto personalAttendanceRecordsDto);
    List<StaffOnJob> selectStaffWithoutAttendanceRecordBeforeTime(@Param("date") LocalDate date, @Param("entryDeadline") LocalDateTime entryDeadline);
    boolean existsAttendanceRecord(@Param("staffOnJobId") Long staffOnJobId, @Param("date") LocalDate date);
}
src/main/java/com/ruoyi/staff/mapper/StaffContractMapper.java
@@ -4,13 +4,9 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.staff.dto.StaffContractDto;
import com.ruoyi.staff.dto.StaffOnJobDto;
import com.ruoyi.staff.pojo.StaffContract;
import com.ruoyi.staff.pojo.StaffOnJob;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface StaffContractMapper extends BaseMapper<StaffContract> {
src/main/java/com/ruoyi/staff/mapper/StaffLeaveMapper.java
@@ -4,12 +4,11 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.staff.dto.StaffLeaveDto;
import com.ruoyi.staff.dto.StaffOnJobDto;
import com.ruoyi.staff.pojo.StaffLeave;
import com.ruoyi.staff.pojo.StaffOnJob;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.util.List;
@@ -18,4 +17,8 @@
    IPage<StaffLeaveDto> staffLeaveListPage(Page page, @Param("c") StaffLeaveDto staffLeaveDto);
    List<StaffLeaveDto> staffLeaveList(@Param("c") StaffLeaveDto staffLeaveDto);
    List<StaffLeaveDto> staffLeaveReasonAnalytics();
    Integer countLeaveByMonth(@Param("monthStart") LocalDate monthStart, @Param("monthEnd") LocalDate monthEnd);
}
src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java
@@ -8,6 +8,7 @@
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.time.LocalDate;
import java.util.List;
@Mapper
@@ -16,4 +17,29 @@
    IPage<StaffOnJobDto> staffOnJobListPage(Page page, @Param("staffOnJob") StaffOnJob staffOnJob);
    List<StaffOnJobDto> staffOnJobList(@Param("staffOnJob") StaffOnJob staffOnJob);
}
    /**
     * ç»Ÿè®¡æŒ‡å®šæ—¥æœŸçš„在职员工数
     *
     * @param date æ—¥æœŸ
     * @return åœ¨èŒå‘˜å·¥æ•°
     */
    Integer countOnJobStaffByDate(@Param("date") LocalDate date);
    /**
     * ç»Ÿè®¡æŒ‡å®šæœˆä»½çš„æ–°å…¥èŒå‘˜å·¥æ•°
     *
     * @param monthStart æœˆä»½å¼€å§‹æ—¥æœŸ
     * @param monthEnd   æœˆä»½ç»“束日期
     * @return æ–°å…¥èŒå‘˜å·¥æ•°
     */
    Integer countNewHireByMonth(@Param("monthStart") LocalDate monthStart, @Param("monthEnd") LocalDate monthEnd);
    /**
     * æ ¹æ®å‘˜å·¥å§“名查询员工信息
     *
     * @param staffName å‘˜å·¥å§“名
     * @return å‘˜å·¥æ•°æ®
     */
    StaffOnJob selectStaffByNickName(String staffName);
}
src/main/java/com/ruoyi/staff/pojo/HolidayApplication.java
@@ -6,7 +6,6 @@
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.time.LocalTime;
@Data
@TableName("holiday_application")
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceLocationConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,59 @@
package com.ruoyi.staff.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.time.LocalTime;
/**
 * <p>
 * äººå‘˜æ‰“卡规则配置
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-11 09:41:34
 */
@Getter
@Setter
@TableName("personal_attendance_location_config")
@ApiModel(value = "PersonalAttendanceLocationConfig对象", description = "人员打卡规则配置")
public class PersonalAttendanceLocationConfig implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    private Integer id;
    @ApiModelProperty("部门id")
    private Integer sysDeptId;
    @ApiModelProperty("地点名称")
    private String locationName;
    @ApiModelProperty("经度")
    private Double longitude;
    @ApiModelProperty("纬度")
    private Double latitude;
    @ApiModelProperty("打卡范围")
    private Double radius;
    @ApiModelProperty("上班时间")
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime startAt;
    @ApiModelProperty("下班时间")
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime  endAt;
}
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceRecords.java
@@ -3,50 +3,78 @@
import com.baomidou.mybatisplus.annotation.*;
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.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.LocalDateTime;
@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")
    @Excel(name = "日期", sort = 1, dateFormat = "yyyy-MM-dd")
    private LocalDate date;
    /**
     * ç­¾åˆ°æ—¶é—´
     */
    @ApiModelProperty("工作开始时间")
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime checkIn;
    /**
     * ç­¾é€€æ—¶é—´
     */
    @Excel(name = "上班时间", sort = 5, dateFormat = "HH:mm")
    private LocalDateTime workStartAt;
    @ApiModelProperty("工作结束时间")
    @JsonFormat(pattern = "HH:mm")
    @DateTimeFormat(pattern = "HH:mm")
    private LocalTime checkOut;
    /**
     * å·¥ä½œæ—¶é•¿
     */
    private String workHours;
    /**
     * çŠ¶æ€
     */
    private String status;
    /**
     * ç§Ÿæˆ·ID
     */
    @Excel(name = "下班时间", sort = 6, dateFormat = "HH:mm")
    private LocalDateTime workEndAt;
    @ApiModelProperty("工作时长")
    @Excel(name = "工时(小时)", sort = 7)
    private BigDecimal workHours;
    @ApiModelProperty("状态 0正常 1迟到 2早退 3迟到早退 4缺勤")
    @Excel(name = "状态", sort = 8,readConverterExp = "0=正常,1=迟到,2=早退,3=迟到、早退,4=缺勤")
    private Integer status;
    @ApiModelProperty("备注")
    @Excel(name = "备注", sort = 9)
    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/pojo/StaffContract.java
@@ -4,7 +4,6 @@
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
src/main/java/com/ruoyi/staff/pojo/StaffLeave.java
@@ -3,13 +3,10 @@
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.Date;
@TableName("staff_leave")
@Data
src/main/java/com/ruoyi/staff/pojo/StaffOnJob.java
@@ -66,7 +66,7 @@
     /**
     * éƒ¨é—¨
     */
    private Integer sysDeptId;
    private Long sysDeptId;
    /**
     * å®¶åº­ä½å€
src/main/java/com/ruoyi/staff/service/AnalyticsService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.staff.service;
import com.ruoyi.staff.dto.StaffLeaveDto;
import com.ruoyi.staff.vo.MonthlyTurnoverRateVo;
import com.ruoyi.staff.vo.TotalTurnoverRateVo;
import java.util.List;
public interface AnalyticsService {
    List<StaffLeaveDto> staffLeaveReasonAnalytics();
    List<MonthlyTurnoverRateVo> getMonthlyTurnoverRateFor12Months();
    /**
     * æŸ¥è¯¢æ€»æµåŠ¨çŽ‡ã€æµå¤±çŽ‡ä»¥åŠåœ¨èŒå‘˜å·¥æ•°
     * @return æ€»ç»Ÿè®¡ç»“æžœ
     */
    TotalTurnoverRateVo getTotalStatistic();
}
src/main/java/com/ruoyi/staff/service/PersonalAttendanceLocationConfigService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.staff.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
/**
 * <p>
 * äººå‘˜æ‰“卡规则配置 æœåŠ¡ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-11 09:41:34
 */
public interface PersonalAttendanceLocationConfigService extends IService<PersonalAttendanceLocationConfig> {
}
src/main/java/com/ruoyi/staff/service/PersonalAttendanceRecordsService.java
@@ -3,8 +3,25 @@
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.pojo.PersonalAttendanceRecords;
import javax.servlet.http.HttpServletResponse;
/**
 * <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(PersonalAttendanceRecordsDto personalAttendanceRecordsDto);
    PersonalAttendanceRecordsDto todayInfo(PersonalAttendanceRecordsDto personalAttendanceRecordsDto);
    void export(HttpServletResponse response, PersonalAttendanceRecordsDto personalAttendanceRecordsDto);
}
src/main/java/com/ruoyi/staff/service/impl/AnalyticsServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,176 @@
package com.ruoyi.staff.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.enums.StaffLeaveReason;
import com.ruoyi.staff.dto.StaffLeaveDto;
import com.ruoyi.staff.mapper.StaffLeaveMapper;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.StaffLeave;
import com.ruoyi.staff.service.AnalyticsService;
import com.ruoyi.staff.vo.MonthlyTurnoverRateVo;
import com.ruoyi.staff.vo.TotalTurnoverRateVo;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@AllArgsConstructor
@Service
public class AnalyticsServiceImpl extends ServiceImpl<StaffLeaveMapper, StaffLeave> implements AnalyticsService {
    @Autowired
    private StaffLeaveMapper staffLeaveMapper;
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
    @Override
    public List<StaffLeaveDto> staffLeaveReasonAnalytics() {
        List<StaffLeaveDto> dbResult = staffLeaveMapper.staffLeaveReasonAnalytics();
        // åˆ›å»ºä¸€ä¸ªMap用于存储所有枚举原因的数量,默认值为0
        Map<String, Integer> reasonCountMap = new HashMap<>();
        for (StaffLeaveReason reasonEnum : StaffLeaveReason.values()) {
            reasonCountMap.put(reasonEnum.getCode(), 0);
        }
        // å°†æ•°æ®åº“查询结果合并到Map中
        for (StaffLeaveDto dto : dbResult) {
            String reasonCode = dto.getReason();
            if (reasonCountMap.containsKey(reasonCode)) {
                reasonCountMap.put(reasonCode, dto.getCount());
            }
        }
        // å°†Map转换为List<StaffLeaveDto>
        List<StaffLeaveDto> result = new ArrayList<>();
        for (StaffLeaveReason reasonEnum : StaffLeaveReason.values()) {
            StaffLeaveDto dto = new StaffLeaveDto();
            dto.setReason(reasonEnum.getCode());
            dto.setCount(reasonCountMap.get(reasonEnum.getCode()));
            dto.setReasonText(reasonEnum.getInfo());
            result.add(dto);
        }
        return result;
    }
    @Override
    public List<MonthlyTurnoverRateVo> getMonthlyTurnoverRateFor12Months() {
        List<MonthlyTurnoverRateVo> result = new ArrayList<>();
        LocalDate now = LocalDate.now();
        DateTimeFormatter monthFormatter = DateTimeFormatter.ofPattern("yyyy-MM");
        // è®¡ç®—è¿‘12个月的数据
        for (int i = 11; i >= 0; i--) {
            LocalDate currentMonth = now.minusMonths(i);
            LocalDate monthStart = currentMonth.withDayOfMonth(1);
            LocalDate monthEnd = currentMonth.withDayOfMonth(currentMonth.lengthOfMonth());
            MonthlyTurnoverRateVo vo = new MonthlyTurnoverRateVo();
            vo.setMonth(currentMonth.format(monthFormatter));
            vo.setMonthStartDate(monthStart);
            vo.setMonthEndDate(monthEnd);
            // æœˆåˆå‘˜å·¥æ•°ï¼ˆä¸Šæœˆæœ«åœ¨èŒå‘˜å·¥æ•°ï¼‰
            LocalDate lastMonthEnd = monthStart.minusDays(1);
            Integer beginMonthStaffCount = staffOnJobMapper.countOnJobStaffByDate(lastMonthEnd);
            vo.setBeginMonthStaffCount(beginMonthStaffCount != null ? beginMonthStaffCount : 0);
            // æœˆæœ«å‘˜å·¥æ•°
            Integer endMonthStaffCount = staffOnJobMapper.countOnJobStaffByDate(monthEnd);
            vo.setEndMonthStaffCount(endMonthStaffCount != null ? endMonthStaffCount : 0);
            // æœˆåº¦å…¥èŒå‘˜å·¥æ•°
            Integer newHireCount = staffOnJobMapper.countNewHireByMonth(monthStart, monthEnd);
            vo.setNewHireCount(newHireCount != null ? newHireCount : 0);
            // æœˆåº¦ç¦»èŒå‘˜å·¥æ•°
            Integer leaveCount = staffLeaveMapper.countLeaveByMonth(monthStart, monthEnd);
            vo.setLeaveCount(leaveCount != null ? leaveCount : 0);
            // è®¡ç®—当期平均在职人数 = (月初员工数 + æœˆæœ«å‘˜å·¥æ•°) / 2
            Double averageStaffCount = (vo.getBeginMonthStaffCount() + vo.getEndMonthStaffCount()) / 2.0;
            // è®¡ç®—流失率:流失率 = æœˆåº¦ç¦»èŒå‘˜å·¥æ•° / å½“期平均在职人数 * 100%
            Double turnoverRate = 0.0;
            if (averageStaffCount > 0) {
                turnoverRate = (double) vo.getLeaveCount() / averageStaffCount * 100;
                // ä¿ç•™ä¸¤ä½å°æ•°
                turnoverRate = Math.round(turnoverRate * 100.0) / 100.0;
            }
            vo.setTurnoverRate(turnoverRate);
            // è®¡ç®—流动率:流动率 = (月度入职员工数 + æœˆåº¦ç¦»èŒå‘˜å·¥æ•°) / å½“期平均在职人数 * 100%
            Double flowRate = 0.0;
            if (averageStaffCount > 0) {
                flowRate = (double) (vo.getNewHireCount() + vo.getLeaveCount()) / averageStaffCount * 100;
                // ä¿ç•™ä¸¤ä½å°æ•°
                flowRate = Math.round(flowRate * 100.0) / 100.0;
            }
            vo.setFlowRate(flowRate);
            result.add(vo);
        }
        return result;
    }
    @Override
    public TotalTurnoverRateVo getTotalStatistic() {
        TotalTurnoverRateVo result = new TotalTurnoverRateVo();
        LocalDate now = LocalDate.now();
        // èŽ·å–å½“å‰åœ¨èŒå‘˜å·¥æ•°
        Integer currentOnJobCount = staffOnJobMapper.countOnJobStaffByDate(now);
        result.setCurrentOnJobCount(currentOnJobCount);
        // èŽ·å–æœ¬æœˆçš„å¼€å§‹å’Œç»“æŸæ—¥æœŸ
        LocalDate monthStartDate = now.withDayOfMonth(1);
        LocalDate monthEndDate = now.withDayOfMonth(now.lengthOfMonth());
        // èŽ·å–æœˆåˆå‘˜å·¥æ•°ï¼ˆå³ä¸Šæœˆæœ«å‘˜å·¥æ•°ï¼‰
        Integer beginMonthStaffCount = staffOnJobMapper.countOnJobStaffByDate(monthStartDate.minusDays(1));
        beginMonthStaffCount = beginMonthStaffCount != null ? beginMonthStaffCount : 0;
        // èŽ·å–æœˆæœ«å‘˜å·¥æ•°
        Integer endMonthStaffCount = staffOnJobMapper.countOnJobStaffByDate(monthEndDate);
        endMonthStaffCount = endMonthStaffCount != null ? endMonthStaffCount : 0;
        // èŽ·å–æœ¬æœˆæ–°å…¥èŒå‘˜å·¥æ•°
        Integer newHireCount = staffOnJobMapper.countNewHireByMonth(monthStartDate, monthEndDate);
        newHireCount = newHireCount != null ? newHireCount : 0;
        // èŽ·å–æœ¬æœˆç¦»èŒå‘˜å·¥æ•°
        Integer leaveCount = staffLeaveMapper.countLeaveByMonth(monthStartDate, monthEndDate);
        leaveCount = leaveCount != null ? leaveCount : 0;
        // è®¡ç®—当期平均在职人数 = (月初员工数 + æœˆæœ«å‘˜å·¥æ•°) / 2
        Double averageStaffCount = (beginMonthStaffCount + endMonthStaffCount) / 2.0;
        // è®¡ç®—总流动率 = (入职人数 + ç¦»èŒäººæ•°) / å½“期平均在职人数 * 100%
        Double totalFlowRate = 0.0;
        if (averageStaffCount > 0) {
            totalFlowRate = (double) (newHireCount + leaveCount) / averageStaffCount * 100;
            // ä¿ç•™ä¸¤ä½å°æ•°
            totalFlowRate = Math.round(totalFlowRate * 100.0) / 100.0;
        }
        result.setTotalFlowRate(totalFlowRate);
        // è®¡ç®—总流失率 = ç¦»èŒäººæ•° / å½“期平均在职人数 * 100%
        Double totalTurnoverRate = 0.0;
        if (averageStaffCount > 0) {
            totalTurnoverRate = (double) leaveCount / averageStaffCount * 100;
            // ä¿ç•™ä¸¤ä½å°æ•°
            totalTurnoverRate = Math.round(totalTurnoverRate * 100.0) / 100.0;
        }
        result.setTotalTurnoverRate(totalTurnoverRate);
        return result;
    }
}
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceLocationConfigServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package com.ruoyi.staff.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.staff.mapper.PersonalAttendanceLocationConfigMapper;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.service.PersonalAttendanceLocationConfigService;
import org.springframework.stereotype.Service;
/**
 * <p>
 * äººå‘˜æ‰“卡规则配置 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-11 09:41:34
 */
@Service
public class PersonalAttendanceLocationConfigServiceImpl extends ServiceImpl<PersonalAttendanceLocationConfigMapper, PersonalAttendanceLocationConfig> implements PersonalAttendanceLocationConfigService {
}
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java
@@ -2,22 +2,257 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
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.PersonalAttendanceLocationConfigMapper;
import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.staff.service.PersonalAttendanceRecordsService;
import com.ruoyi.staff.utils.LocationUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.List;
/**
 * <p>
 *  æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-09 01:20:07
 */
@Service
@Transactional(rollbackFor = Exception.class)
public class PersonalAttendanceRecordsServiceImpl extends ServiceImpl<PersonalAttendanceRecordsMapper, PersonalAttendanceRecords> implements PersonalAttendanceRecordsService {
    @Autowired
    private PersonalAttendanceRecordsMapper personalAttendanceRecordsMapper;
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
    @Autowired
    private PersonalAttendanceLocationConfigMapper personalAttendanceLocationConfigMapper;
    @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));
    public int add(PersonalAttendanceRecordsDto personalAttendanceRecordsDto) {
        // å½“前时间
        LocalDate currentDate = LocalDate.now();
        LocalDateTime currentDateTime = LocalDateTime.now();
        /*查询员工信息*/
        QueryWrapper<StaffOnJob> staffQueryWrapper = new QueryWrapper<>();
        staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername());
        staffQueryWrapper.eq("staff_state", 1);//在职
        StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper);
        if (staffOnJob == null) {
            throw new BaseException("当前用户没有对应的员工信息");
        }
        /*判断打卡位置是否在规则范围内*/
        List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(Wrappers.<PersonalAttendanceLocationConfig>lambdaQuery()
                .eq(PersonalAttendanceLocationConfig::getSysDeptId, staffOnJob.getSysDeptId())
                .orderByDesc(PersonalAttendanceLocationConfig::getId));
        if (personalAttendanceLocationConfigs == null || personalAttendanceLocationConfigs.isEmpty()) {
            throw new BaseException("当前部门没有设置打卡规则");
        }
        Double punchLongitude = personalAttendanceRecordsDto.getLongitude(); //打卡的经度
        Double punchLatitude = personalAttendanceRecordsDto.getLatitude(); // æ‰“卡的纬度
        if (punchLongitude == null || punchLatitude == null) {
            throw new BaseException("打卡失败:未获取到您的位置信息,请开启定位权限");
        }
        //计算打卡位置与考勤点的距离
        PersonalAttendanceLocationConfig locationConfig = personalAttendanceLocationConfigs.get(0);//获取最新的一条数据
        double allowedRadius = locationConfig.getRadius(); // å…è®¸çš„范围(米)
        double actualDistance = LocationUtils.calculateDistance(
                punchLatitude, punchLongitude, // å‘˜å·¥æ‰“卡的经纬度
                locationConfig.getLatitude(), locationConfig.getLongitude() // è€ƒå‹¤ç‚¹çš„经纬度
        );
        //判断是否在范围内
        if (actualDistance > allowedRadius) {
            throw new BaseException(String.format("打卡失败:您当前位置距离考勤点%.2f米,超出允许范围(%s米)", actualDistance, allowedRadius));
        }
        /*判断打卡时间*/
        // æ ¹æ®å‘˜å·¥ID和当前日期查询打卡记录
        QueryWrapper<PersonalAttendanceRecords> attendanceQueryWrapper = new QueryWrapper<>();
        attendanceQueryWrapper.eq("staff_on_job_id", staffOnJob.getId())
                .eq("date", currentDate);
        PersonalAttendanceRecords attendanceRecord = personalAttendanceRecordsMapper.selectOne(attendanceQueryWrapper);
        // æ ¹æ®è€ƒå‹¤æ—¶é—´åˆ¤æ–­è¿Ÿåˆ°æ—©é€€
        if (attendanceRecord == null) {
            // ä¸å­˜åœ¨æ‰“卡记录,创建新记录
            PersonalAttendanceRecords personalAttendanceRecords = new PersonalAttendanceRecords();
            personalAttendanceRecords.setStaffOnJobId(staffOnJob.getId());
            personalAttendanceRecords.setDate(currentDate);
            personalAttendanceRecords.setWorkStartAt(currentDateTime);
            personalAttendanceRecords.setStatus(determineAttendanceStatus(personalAttendanceRecords, true,locationConfig));
            personalAttendanceRecords.setRemark(personalAttendanceRecords.getRemark());
            personalAttendanceRecords.setTenantId(staffOnJob.getTenantId());
            return personalAttendanceRecordsMapper.insert(personalAttendanceRecords);
        } else {
            if (attendanceRecord.getWorkEndAt() == null) {
                // æ›´æ–°å·¥ä½œç»“束时间和工作时长
                attendanceRecord.setWorkEndAt(currentDateTime);
                // è®¡ç®—工作时长(精确到分钟,保留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, false,locationConfig));
                return personalAttendanceRecordsMapper.updateById(attendanceRecord);
            } else {
                throw new BaseException("您已经打过卡了,无需重复打卡!!!");
            }
        }
    }
    // æ ¹æ®å®žé™…时间和是否上班时间判断考勤状态
    // 0 æ­£å¸¸ 1 è¿Ÿåˆ° 2 æ—©é€€ 3 è¿Ÿåˆ°æ—©é€€ 4 ç¼ºå‹¤
    private Integer determineAttendanceStatus(PersonalAttendanceRecords attendanceRecord, boolean isStart,PersonalAttendanceLocationConfig locationConfig) {
        //判断是上班打卡还是下班打卡
        LocalDateTime actualTime = isStart ? attendanceRecord.getWorkStartAt() : attendanceRecord.getWorkEndAt();
        try {
            // èŽ·å–è€ƒå‹¤æ—¶é—´é…ç½®
            LocalTime startAt = locationConfig.getStartAt();//上班时间
            LocalTime  endAt = locationConfig.getEndAt();//下班时间
            LocalTime  timeConfig = isStart ? startAt : endAt;
            // è§£æžå°æ—¶å’Œåˆ†é’Ÿ
            int standardHour = timeConfig.getHour();
            int standardMinute = timeConfig.getMinute();
            // èŽ·å–å®žé™…æ—¶é—´çš„æ—¶åˆ†
            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)) {
                    if (attendanceRecord.getStatus() == 1) {
                        return 3; // è¿Ÿåˆ°æ—©é€€
                    }
                    return 2; // æ—©é€€
                }else if (attendanceRecord.getStatus() == 1) {
                    return 1; // ä¸‹ç­æ‰“卡正常但是上班迟到
                }
            }
            return 0; // æ­£å¸¸
        } catch (Exception e) {
            // å¦‚果获取配置失败,默认返回正常状态
            log.warn("获取考勤时间配置失败,使用默认状态:" + e.getMessage());
            return 0;
        }
    }
    @Override
    public IPage<PersonalAttendanceRecordsDto> listPage(Page page, PersonalAttendanceRecordsDto personalAttendanceRecordsDto) {
        boolean admin = SecurityUtils.isAdmin(SecurityUtils.getUserId());
        if (!admin) {
            QueryWrapper<StaffOnJob> staffQueryWrapper = new QueryWrapper<>();
            staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername());
            staffQueryWrapper.eq("staff_state", 1);//在职
            StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper);
            if (staffOnJob == null) {
                return new Page<>(page.getCurrent(), page.getSize(), 0);
            }
            personalAttendanceRecordsDto.setStaffOnJobId(staffOnJob.getId());
        }
        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());
        staffQueryWrapper.eq("staff_state", 1);//在职
        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);
        // è¿”回参数
        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);
        //获取该员工对应的打卡规则
        List<PersonalAttendanceLocationConfig> personalAttendanceLocationConfigs = personalAttendanceLocationConfigMapper.selectList(Wrappers.<PersonalAttendanceLocationConfig>lambdaQuery()
                .eq(PersonalAttendanceLocationConfig::getSysDeptId, staffOnJob.getSysDeptId())
                .orderByDesc(PersonalAttendanceLocationConfig::getId));
        if (personalAttendanceLocationConfigs.size()>0){
            resultDto.setStartAt(personalAttendanceLocationConfigs.get(0).getStartAt());
            resultDto.setEndAt(personalAttendanceLocationConfigs.get(0).getEndAt());
        }
        return resultDto;
    }
    @Override
    public void export(HttpServletResponse response, PersonalAttendanceRecordsDto personalAttendanceRecordsDto) {
        boolean admin = SecurityUtils.isAdmin(SecurityUtils.getUserId());
        if (!admin) {
            QueryWrapper<StaffOnJob> staffQueryWrapper = new QueryWrapper<>();
            staffQueryWrapper.eq("staff_no", SecurityUtils.getUsername());
            staffQueryWrapper.eq("staff_state", 1);//在职
            StaffOnJob staffOnJob = staffOnJobMapper.selectOne(staffQueryWrapper);
            if (staffOnJob == null) {
                throw new ServiceException("没有员工信息,无法导出考勤记录");
            }
            personalAttendanceRecordsDto.setStaffOnJobId(staffOnJob.getId());
        }
        List<PersonalAttendanceRecordsDto> personalAttendanceRecords = personalAttendanceRecordsMapper.listPage(new Page<>(1, Integer.MAX_VALUE), personalAttendanceRecordsDto).getRecords();
        ExcelUtil<PersonalAttendanceRecordsDto> util = new ExcelUtil<PersonalAttendanceRecordsDto>(PersonalAttendanceRecordsDto.class);
        util.exportExcel(response, personalAttendanceRecords, "考勤记录导出");
    }
}
src/main/java/com/ruoyi/staff/service/impl/StaffLeaveServiceImpl.java
@@ -5,14 +5,17 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.project.system.domain.SysUser;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.staff.dto.StaffLeaveDto;
import com.ruoyi.staff.mapper.StaffLeaveMapper;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.StaffLeave;
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.staff.service.StaffLeaveService;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.staff.pojo.StaffLeave;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
@@ -23,9 +26,14 @@
@AllArgsConstructor
@Service
public class StaffLeaveServiceImpl extends ServiceImpl<StaffLeaveMapper, StaffLeave> implements StaffLeaveService {
    @Autowired
    private StaffLeaveMapper staffLeaveMapper;
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
    @Autowired
    private SysUserMapper sysUserMapper;
    //新增离职列表分页查询
    @Override
@@ -51,11 +59,20 @@
        // æ–°å¢žç¦»èŒè®°å½•
        StaffLeave staffLeave = new StaffLeave();
        staffLeave.setStaffOnJobId(staffLeaveDto.getStaffOnJobId());
        staffLeave.setReason(staffLeaveDto.getReason());
        String reason = staffLeaveDto.getReason();
        if (!StaffLeaveReasonOther.getCode().equals(reason)){
            staffLeave.setRemark("");
        if (StaffLeaveReasonOther.getCode().equals(reason)){
            staffLeave.setRemark(staffLeaveDto.getRemark());
        }
        staffLeaveMapper.insert(staffLeave);
        // æ›´æ–°å¯¹åº”用户状态为停用
        // æ ¹æ®å‘˜å·¥ç¼–号查询用户
        SysUser sysUser = sysUserMapper.selectUserByUserName(staffOnJob.getStaffNo());
        if (sysUser != null) {
            sysUser.setStatus("1");
            sysUserMapper.updateUser(sysUser);
        }
        // æ›´æ–°ç¦»èŒçŠ¶æ€ä¸ºç¦»èŒ
        staffOnJob.setStaffState(0);
@@ -97,6 +114,5 @@
        ExcelUtil<StaffLeaveDto> util = new ExcelUtil<StaffLeaveDto>(StaffLeaveDto.class);
        util.exportExcel(response, staffLeaves, "员工离职导出");
    }
}
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java
@@ -21,30 +21,37 @@
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@AllArgsConstructor
@Service
public class StaffOnJobServiceImpl extends ServiceImpl<StaffOnJobMapper, StaffOnJob>  implements IStaffOnJobService {
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
    @Autowired
    private SysPostMapper sysPostMapper;
    @Autowired
    private StaffContractMapper staffContractMapper;
    @Autowired
    private StaffLeaveMapper staffLeaveMapper;
@@ -66,6 +73,7 @@
        }
        // åˆ›å»ºå…¥èŒæ•°æ®
        staffOnJobPrams.setContractExpireTime(staffOnJobPrams.getContractEndTime());
        staffOnJobPrams.setStaffState(1);
        staffOnJobMapper.insert(staffOnJobPrams);
        // åˆ›å»ºåˆåŒè®°å½•
@@ -87,7 +95,7 @@
            throw new BaseException("编号为"+staffOnJobParams.getStaffNo()+"的员工不存在,无法更新!!!");
        }
        String[] ignoreProperties = {"id"};//排除id属性
        String[] ignoreProperties = {"id"};//排除更新属性
        // èŽ·å–æœ€æ–°åˆåŒæ•°æ®ï¼Œå¹¶ä¸”æ›´æ–°
        StaffContract contract = staffContractMapper.selectOne(Wrappers.<StaffContract>lambdaQuery()
@@ -100,9 +108,8 @@
        }
        // æ›´æ–°å‘˜å·¥æ•°æ®
        BeanUtils.copyProperties(staffOnJobParams,job,ignoreProperties);
        job.setContractExpireTime(staffOnJobParams.getContractEndTime());
        return staffOnJobMapper.updateById(job);
        staffOnJobParams.setContractExpireTime(staffOnJobParams.getContractEndTime());
        return staffOnJobMapper.updateById(staffOnJobParams);
    }
    //删除入职
@@ -152,8 +159,12 @@
        StaffOnJobDto staffOnJobDto = new StaffOnJobDto();
        BeanUtils.copyProperties(staffOnJob, staffOnJobDto);
        // æŸ¥è¯¢å²—位名称
        SysPost post = sysPostMapper.selectPostById((long) staffOnJob.getSysPostId());
        staffOnJobDto.setPostName(post.getPostName());
        if (staffOnJob.getSysPostId() != null) {
            SysPost post = sysPostMapper.selectPostById(staffOnJob.getSysPostId().longValue());
            if (post != null) {
                staffOnJobDto.setPostName(post.getPostName());
            }
        }
        // æŸ¥è¯¢åˆåŒä¿¡æ¯
        StaffContract contract = staffContractMapper.selectOne(Wrappers.<StaffContract>lambdaQuery()
@@ -173,7 +184,7 @@
    public void staffOnJobExport(HttpServletResponse response, StaffOnJob staffOnJob) {
        List<StaffOnJobDto> staffOnJobs = staffOnJobMapper.staffOnJobList(staffOnJob);
        ExcelUtil<StaffOnJobDto> util = new ExcelUtil<StaffOnJobDto>(StaffOnJobDto.class);
        util.exportExcel(response, staffOnJobs, "在职员工台账导出");
        util.exportExcel(response, staffOnJobs, "员工台账导出");
    }
    @Override
src/main/java/com/ruoyi/staff/task/PersonalAttendanceRecordsTask.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,75 @@
package com.ruoyi.staff.task;
import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.staff.service.PersonalAttendanceRecordsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
/**
 * ä¸ªäººè€ƒå‹¤è®°å½•定时任务
 *
 * @author èŠ¯å¯¼è½¯ä»¶ï¼ˆæ±Ÿè‹ï¼‰æœ‰é™å…¬å¸
 * @since 2026-02-09
 */
@Slf4j
@Component
public class PersonalAttendanceRecordsTask {
    @Autowired
    private PersonalAttendanceRecordsMapper personalAttendanceRecordsMapper;
    @Autowired
    private PersonalAttendanceRecordsService personalAttendanceRecordsService;
    /**
     * æ¯å¤©å‡Œæ™¨ç”Ÿæˆæ˜¨æ—¥çš„缺勤记录
     * å®šæ—¶ä»»åŠ¡ï¼šæ¯å¤©å‡Œæ™¨1点执行
     * æŽ’除今天刚入职的员工
     */
    @Scheduled(cron = "0 0 1 * * ?")
    public void generateAbsenceRecords() {
        try {
            // èŽ·å–æ˜¨æ—¥æ—¥æœŸ
            LocalDate yesterday = LocalDate.now().minusDays(1);
            // ç›´æŽ¥æŸ¥è¯¢æ˜¨å¤©æ²¡æœ‰è€ƒå‹¤è®°å½•的在职员工(排除今天刚入职的)
            LocalDateTime todayStart = LocalDate.now().atStartOfDay();
            List<StaffOnJob> staffWithoutAttendance = personalAttendanceRecordsMapper.selectStaffWithoutAttendanceRecordBeforeTime(yesterday, todayStart);
            // éåŽ†æ²¡æœ‰è€ƒå‹¤è®°å½•çš„å‘˜å·¥ï¼Œç”Ÿæˆç¼ºå‹¤è®°å½•
            for (StaffOnJob staff : staffWithoutAttendance) {
                try {
                    boolean exists = personalAttendanceRecordsMapper.existsAttendanceRecord(staff.getId(), yesterday);
                    if (exists) {
                        continue;
                    }
                    PersonalAttendanceRecords absenceRecord = new PersonalAttendanceRecords();
                    absenceRecord.setStaffOnJobId(staff.getId());
                    absenceRecord.setDate(yesterday);
                    absenceRecord.setStatus(4); // è®¾ç½®çŠ¶æ€ä¸ºç¼ºå‹¤
                    absenceRecord.setRemark("系统自动生成-缺勤");
                    absenceRecord.setCreateTime(LocalDateTime.now());
                    absenceRecord.setUpdateTime(LocalDateTime.now());
                    absenceRecord.setTenantId(staff.getTenantId());
                    personalAttendanceRecordsService.save(absenceRecord);
                } catch (Exception e) {
                    log.error("为员工{}生成缺勤记录失败:{}", staff.getStaffName(), e.getMessage(), e);
                }
            }
            log.info("昨日缺勤记录生成完成");
        } catch (Exception e) {
            log.error("生成昨日缺勤记录任务执行失败:{}", e.getMessage(), e);
        }
    }
}
src/main/java/com/ruoyi/staff/utils/LocationUtils.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package com.ruoyi.staff.utils;
// å·¥å…·ç±»ï¼šè®¡ç®—两个经纬度之间的距离(球面距离)
public class LocationUtils {
    private static final double EARTH_RADIUS = 6371000; // åœ°çƒåŠå¾„,单位米
    /**
     * è®¡ç®—两个经纬度之间的距离(米)
     * @param lat1 ç¬¬ä¸€ä¸ªç‚¹çº¬åº¦
     * @param lon1 ç¬¬ä¸€ä¸ªç‚¹ç»åº¦
     * @param lat2 ç¬¬äºŒä¸ªç‚¹çº¬åº¦
     * @param lon2 ç¬¬äºŒä¸ªç‚¹ç»åº¦
     * @return è·ç¦»ï¼ˆç±³ï¼‰
     */
    public static double calculateDistance(double lat1, double lon1, double lat2, double lon2) {
        // è½¬å¼§åº¦
        double radLat1 = Math.toRadians(lat1);
        double radLon1 = Math.toRadians(lon1);
        double radLat2 = Math.toRadians(lat2);
        double radLon2 = Math.toRadians(lon2);
        // å·®å€¼
        double deltaLat = radLat1 - radLat2;
        double deltaLon = radLon1 - radLon2;
        // çƒé¢è·ç¦»å…¬å¼
        double distance = 2 * Math.asin(Math.sqrt(
                Math.pow(Math.sin(deltaLat / 2), 2) +
                        Math.cos(radLat1) * Math.cos(radLat2) *
                                Math.pow(Math.sin(deltaLon / 2), 2)
        ));
        distance = distance * EARTH_RADIUS;
        // ä¿ç•™ä¸¤ä½å°æ•°
        distance = Math.round(distance * 100) / 100.0;
        return distance;
    }
}
src/main/java/com/ruoyi/staff/vo/MonthlyTurnoverRateVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
package com.ruoyi.staff.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDate;
/**
 * æœˆåº¦å‘˜å·¥æµåŠ¨çŽ‡å’Œæµå¤±çŽ‡ç»Ÿè®¡VO
 */
@Data
public class MonthlyTurnoverRateVo {
    @ApiModelProperty("月份")
    private String month;
    @ApiModelProperty("月初员工数")
    private Integer beginMonthStaffCount;
    @ApiModelProperty("月末员工数")
    private Integer endMonthStaffCount;
    @ApiModelProperty("月度入职员工数")
    private Integer newHireCount;
    @ApiModelProperty("月度离职员工数")
    private Integer leaveCount;
    @ApiModelProperty("流失率(%)")
    private Double turnoverRate;
    @ApiModelProperty("流动率(%)")
    private Double flowRate;
    @ApiModelProperty("月份开始日期")
    private LocalDate monthStartDate;
    @ApiModelProperty("月份结束日期")
    private LocalDate monthEndDate;
}
src/main/java/com/ruoyi/staff/vo/TotalTurnoverRateVo.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.staff.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * å‘˜å·¥æ€»æµåŠ¨çŽ‡ã€æµå¤±çŽ‡åŠåœ¨èŒå‘˜å·¥æ•°ç»Ÿè®¡VO
 */
@Data
public class TotalTurnoverRateVo {
    @ApiModelProperty("总流动率(%)")
    private Double totalFlowRate;
    @ApiModelProperty("总流失率(%)")
    private Double totalTurnoverRate;
    @ApiModelProperty("当前在职员工数")
    private Integer currentOnJobCount;
}
src/main/resources/mapper/production/ProductOrderMapper.xml
@@ -31,9 +31,6 @@
            <if test="c.salesContractNo != null and c.salesContractNo != ''">
                and sl.sales_contract_no like concat('%',#{c.salesContractNo},'%')
            </if>
            <if test="c.customerName != null and c.customerName != ''">
                and sl.customer_name like concat('%',#{c.customerName},'%')
            </if>
            <if test="c.productCategory != null and c.productCategory != ''">
                and slp.product_category like concat('%',#{c.productCategory},'%')
            </if>
@@ -46,7 +43,7 @@
        </where>
    </select>
    <select id="productMainByOrderId" resultType="com.ruoyi.production.dto.ProductOrderDto">
        select po.*,
        select po.*,F
               pwo.work_order_no,
               pwo.report_work,
               pwo.status,