9a9f8f9bffc958bdd4daa315c6f393a9d3850543..e8d08ebdd187f99f793cd839038dd5c392cdfbd8
4 天以前 gongchunyi
fix: 修改人员薪资接口
e8d08e 对比 | 目录
4 天以前 zss
空数据返回null
5d743b 对比 | 目录
4 天以前 huminmin
Merge branch 'dev_New' of http://114.132.189.42:9002/r/product-inventory-ma...
8050be 对比 | 目录
4 天以前 huminmin
打卡签到列表和新增
cd3cb0 对比 | 目录
4 天以前 huminmin
打卡签到列表和新增
5f19e0 对比 | 目录
4 天以前 liyong
feat(quality): 添加不合格品处理标识字段
86aad3 对比 | 目录
4 天以前 liyong
feat(quality): 添加不合格品处理标识字段
ce8013 对比 | 目录
4 天以前 liyong
feat(quality): 添加不合格品处理标识字段
57d35c 对比 | 目录
4 天以前 liyong
fix(quality): 修复质量不合格处理逻辑并优化查询性能
bf3858 对比 | 目录
4 天以前 liyong
Merge remote-tracking branch 'origin/dev_New' into dev_New
23cb70 对比 | 目录
4 天以前 liyong
fix(quality): 修复质量检验统计中不合格项目的数据过滤问题
e644ee 对比 | 目录
4 天以前 gongchunyi
Merge remote-tracking branch 'origin/dev_New' into dev_New
780d2a 对比 | 目录
4 天以前 gongchunyi
fix: 质量管理检查日期查询去除时分秒
8abed8 对比 | 目录
4 天以前 liyong
Merge remote-tracking branch 'origin/dev_New' into dev_New
2f22c6 对比 | 目录
4 天以前 liyong
fix(production): 修正生产统计数据计算逻辑
6f9a6a 对比 | 目录
4 天以前 maven
yys 采购审批查询采购台账附件,发货也是
54208f 对比 | 目录
4 天以前 gongchunyi
Merge remote-tracking branch 'origin/dev_New' into dev_New
7791d7 对比 | 目录
4 天以前 gongchunyi
fix: APP路由拼接格式错误
fc802e 对比 | 目录
4 天以前 liyong
Merge remote-tracking branch 'origin/dev_New' into dev_New
ffbe50 对比 | 目录
4 天以前 liyong
fix(sales): 修复销售 ledger 产品库存充足判断逻辑
723adf 对比 | 目录
4 天以前 gongchunyi
feat: 保存通知公告数据时进行APP消息推送
be82fb 对比 | 目录
4 天以前 gongchunyi
chore: APP消息推送SDK
a67836 对比 | 目录
已添加9个文件
已修改34个文件
1526 ■■■■ 文件已修改
doc/20260209_create_personal_attendance_records.sql 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 83 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/controller/CompensationPerformanceController.java 30 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/mapper/CompensationPerformanceMapper.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/pojo/CompensationPerformance.java 207 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/service/CompensationPerformanceService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/service/impl/CompensationPerformanceServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysUserClientController.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/GetuiConfig.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysUserClient.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/mapper/SysMenuMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/mapper/SysUserClientMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/SysUserClientService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysUserClientServiceImpl.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/mapper/QualityUnqualifiedMapper.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/IQualityUnqualifiedService.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/HolidayApplicationController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java 54 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceRecordsMapper.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceRecords.java 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffOnJob.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/PersonalAttendanceRecordsService.java 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java 184 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-new.yml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/compensationperformance/CompensationPerformanceMapper.xml 19 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductProcessMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductOutputMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/StaffOnJobMapper.xml 25 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysMenuMapper.xml 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260209_create_personal_attendance_records.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
#员工考勤表
drop table if exists personal_attendance_records;
create table personal_attendance_records
(
    id              bigint auto_increment primary key,
    staff_on_job_id bigint not null default 0 comment '员工在职id',
    date            date not null comment '日期',
    work_start_at   datetime null comment '工作开始时间',
    work_end_at     datetime null comment '工作结束时间',
    work_hours      decimal(5,2) null comment '工作时长',
    status          tinyint not null default 0 comment '状态 0正常 1迟到 2早退',
    remark          text null comment '备注',
    tenant_id       bigint not null comment '租户id',
    create_time     datetime null comment '录入时间',
    update_time     datetime null comment '更新时间',
    index idx_staff_on_job_id (staff_on_job_id)
);
pom.xml
@@ -16,7 +16,7 @@
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.15</version>
        <relativePath />
        <relativePath/>
    </parent>
    <properties>
@@ -43,6 +43,7 @@
        <spring-security.version>5.7.12</spring-security.version>
        <spring-framework.version>5.3.39</spring-framework.version>
        <mybatis-plus.version>3.5.3.1</mybatis-plus.version>
        <getui-sdk.version>1.0.7.0</getui-sdk.version>
    </properties>
    <dependencies>
@@ -269,7 +270,6 @@
        </dependency>
        <!-- minio -->
        <dependency>
            <groupId>io.minio</groupId>
@@ -301,51 +301,58 @@
            <artifactId>easyexcel</artifactId>
            <version>4.0.3</version>
        </dependency>
         <dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.3</version>
        </dependency>
        <dependency>
            <groupId>com.getui.push</groupId>
            <artifactId>restful-sdk</artifactId>
            <version>${getui-sdk.version}</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork> <!-- å¦‚果没有该配置,devtools不会生效 -->
                </configuration>
            </plugin>
    <build>
        <finalName>${project.artifactId}</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork> <!-- å¦‚果没有该配置,devtools不会生效 -->
                </configuration>
            </plugin>
        </plugins>
    </build>
    </build>
    <repositories>
        <repository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>
    <repositories>
        <repository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
    </repositories>
    <pluginRepositories>
        <pluginRepository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
    <pluginRepositories>
        <pluginRepository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>
</project>
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -166,6 +166,20 @@
                    .eq(CommonFile::getCommonId, record.getId())
                    .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue()));
            record.setCommonFileList(commonFiles);
            // é‡‡è´­å®¡æ‰¹æŸ¥è¯¢é‡‡è´­é™„ä»¶
            if (approveProcess.getApproveType() == 5) {
                List<CommonFile> commonFiles1 = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                        .eq(CommonFile::getCommonId, record.getId())
                        .eq(CommonFile::getType, FileNameType.PURCHASE.getValue()));
                record.setCommonFileList(commonFiles1);
            }
            // å‘货审批查询发货附件
            if (approveProcess.getApproveType() == 7) {
                List<CommonFile> commonFiles1 = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                        .eq(CommonFile::getCommonId, record.getId())
                        .eq(CommonFile::getType, FileNameType.SHIP.getValue()));
                record.setCommonFileList(commonFiles1);
            }
        }
        return approveProcessIPage;
    }
src/main/java/com/ruoyi/compensationperformance/controller/CompensationPerformanceController.java
@@ -40,11 +40,14 @@
    @Autowired
    private CompensationPerformanceService compensationPerformanceService;
    @Autowired
    private StaffOnJobMapper staffOnJobMapper;
    @GetMapping("/listPage")
    @Log(title = "薪酬绩效-分页查询", businessType = BusinessType.OTHER)
    @ApiOperation("薪酬绩效-分页查询")
    public AjaxResult listPage(Page page, CompensationPerformance compensationPerformance){
        IPage<CompensationPerformance> listPage = compensationPerformanceService.listPage(page, compensationPerformance);
    public AjaxResult listPage(Page page, String staffName, String payDateStr) {
        IPage<CompensationPerformance> listPage = compensationPerformanceService.listPage(page, staffName, payDateStr);
        return AjaxResult.success(listPage);
    }
@@ -52,7 +55,7 @@
    @Log(title = "薪酬绩效-添加", businessType = BusinessType.INSERT)
    @ApiOperation("薪酬绩效-添加")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult add(@RequestBody CompensationPerformance compensationPerformance){
    public AjaxResult add(@RequestBody CompensationPerformance compensationPerformance) {
        boolean save = compensationPerformanceService.save(compensationPerformance);
        return save ? AjaxResult.success("添加成功") : AjaxResult.error("添加失败");
    }
@@ -61,7 +64,7 @@
    @Log(title = "薪酬绩效-修改", businessType = BusinessType.UPDATE)
    @ApiOperation("薪酬绩效-修改")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult update(@RequestBody CompensationPerformance compensationPerformance){
    public AjaxResult update(@RequestBody CompensationPerformance compensationPerformance) {
        boolean update = compensationPerformanceService.updateById(compensationPerformance);
        return update ? AjaxResult.success("修改成功") : AjaxResult.error("修改失败");
    }
@@ -70,17 +73,17 @@
    @Log(title = "薪酬绩效-删除", businessType = BusinessType.DELETE)
    @ApiOperation("薪酬绩效-删除")
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult delete(@RequestBody List<Long> ids){
        if(CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
    public AjaxResult delete(@RequestBody List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) return AjaxResult.error("请传入要删除的ID");
        boolean delete = compensationPerformanceService.removeBatchByIds(ids);
        return delete ? AjaxResult.success("删除成功") : AjaxResult.error("删除失败");
    }
    @Log(title = "导出薪资管理列表", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response ) {
    public void export(HttpServletResponse response) {
        List<CompensationPerformance> list = compensationPerformanceService.list();
        ExcelUtil<CompensationPerformance> util = new ExcelUtil<CompensationPerformance>(CompensationPerformance.class);
        ExcelUtil<CompensationPerformance> util = new ExcelUtil<>(CompensationPerformance.class);
        util.exportExcel(response, list, "导出薪资管理列表");
    }
@@ -92,18 +95,15 @@
        util.exportExcel(response, list, "下载薪资管理列表模板");
    }
    @Autowired
    private SysUserMapper sysUserMapper;
    @Log(title = "导入薪资管理列表", businessType = BusinessType.IMPORT)
    @PostMapping("/importData")
    public AjaxResult importData(MultipartFile file) throws Exception {
        ExcelUtil<CompensationPerformance> util = new ExcelUtil<>(CompensationPerformance.class);
        List<CompensationPerformance> list = util.importExcel(file.getInputStream());
        list.forEach(item->{
            SysUser staffOnJob = sysUserMapper.selectUserByNickName(item.getName());
            if(staffOnJob!=null){
                item.setStaffId(staffOnJob.getUserId());
        list.forEach(item -> {
            StaffOnJob staffOnJob = staffOnJobMapper.selectStaffByNickName(item.getStaffName());
            if (staffOnJob != null) {
                item.setStaffId(staffOnJob.getId());
            }
        });
        boolean b = compensationPerformanceService.saveBatch(list);
src/main/java/com/ruoyi/compensationperformance/mapper/CompensationPerformanceMapper.java
@@ -15,9 +15,10 @@
    /**
     * åˆ†é¡µæŸ¥è¯¢
     *
     * @param page
     * @param compensationPerformance
     * @param page       åˆ†é¡µ
     * @param staffName  å‘˜å·¥å§“名
     * @param payDateStr è–ªèµ„日期
     * @return
     */
    IPage<CompensationPerformance> listPage(Page page,@Param("req") CompensationPerformance compensationPerformance);
    IPage<CompensationPerformance> listPage(Page page, @Param("staffName") String staffName, @Param("payDateStr") String payDateStr);
}
src/main/java/com/ruoyi/compensationperformance/pojo/CompensationPerformance.java
@@ -8,193 +8,156 @@
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Date;
/**
 * è–ªé…¬ç»©æ•ˆæ˜Žç»†
 *
 * @author :yys
 * @date : 2025/8/8 9:40
 * @date : 2025/8/8
 */
@Data
@TableName("compensation_performance")
@ApiModel
public class CompensationPerformance {
@ApiModel("薪酬绩效明细")
public class CompensationPerformance implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    @ApiModelProperty("主键")
    private Long id;
    /**
     * ç”¨æˆ·id
     * å‘˜å·¥id
     */
    @ApiModelProperty("用户id")
    @ApiModelProperty("员工id")
    private Long staffId;
    /**
     * å§“名
     * å‘˜å·¥å§“名
     */
    @ApiModelProperty("姓名")
    @Excel(name = "姓名")
    private String name;
    @ApiModelProperty("员工姓名")
    @Excel(name = "员工姓名")
    @TableField(exist = false)
    private String staffName;
    /**
     * è–ªèµ„月份(查询)
     * å²—位名称
     */
    @ApiModelProperty("薪资月份(查询)")
    @ApiModelProperty("岗位名称")
    @Excel(name = "岗位名称")
    @TableField(exist = false)
    private String payDateStr;
    private String postName;
    /**
     * éƒ¨é—¨åç§°
     */
    @ApiModelProperty("部门名称")
    @Excel(name = "部门名称")
    @TableField(exist = false)
    private String deptName;
    /**
     * è–ªèµ„月份
     */
    @ApiModelProperty("薪资月份")
    @Excel(name = "薪资月份", dateFormat = "yyyy-MM", width = 30)
    @Excel(name = "月份", dateFormat = "yyyy-MM", width = 20)
    @JsonFormat(pattern = "yyyy-MM", timezone = "GMT+8")
    @DateTimeFormat(pattern = "yyyy-MM")
    private Date payDate;
    /**
     * åº”出勤天数
     */
    @ApiModelProperty("应出勤天数")
//    @Excel(name = "应出勤天数")
    private BigDecimal shouldAttendedNum;
    /**
     * å®žé™…出勤天数
     */
    @ApiModelProperty("实际出勤天数")
//    @Excel(name = "实际出勤天数")
    private BigDecimal actualAttendedNum;
    /**
     * åŸºæœ¬å·¥èµ„
     */
    @ApiModelProperty("基本工资")
//    @Excel(name = "基本工资")
    @Excel(name = "基本工资")
    private BigDecimal basicSalary;
    /**
     * å²—位工资
     * è®¡ä»¶å·¥èµ„
     */
    @ApiModelProperty("岗位工资")
//    @Excel(name = "岗位工资")
    private BigDecimal postSalary;
    @ApiModelProperty("计件工资")
    @Excel(name = "计件工资")
    private BigDecimal pieceworkSalary;
    /**
     * å…¥ç¦»èŒç¼ºå‹¤æ‰£æ¬¾
     * è®¡æ—¶å·¥èµ„
     */
    @ApiModelProperty("入离职缺勤扣款")
//    @Excel(name = "入离职缺勤扣款")
    private BigDecimal deductionAbsenteeism;
    @ApiModelProperty("计时工资")
    @Excel(name = "计时工资")
    private BigDecimal hourlySalary;
    /**
     * ç—…假扣款
     * å…¶ä»–æ”¶å…¥
     */
    @ApiModelProperty("病假扣款")
//    @Excel(name = "病假扣款")
    private BigDecimal sickLeaveDeductions;
    /**
     * äº‹å‡æ‰£æ¬¾
     */
    @ApiModelProperty("事假扣款")
//    @Excel(name = "事假扣款")
    private BigDecimal deductionPersonalLeave;
    /**
     * å¿˜è®°æ‰“卡扣款
     */
    @ApiModelProperty("忘记打卡扣款")
//    @Excel(name = "忘记打卡扣款")
    private BigDecimal forgetClockDeduct;
    /**
     * ç»©æ•ˆå¾—分
     */
    @ApiModelProperty("绩效得分")
//    @Excel(name = "绩效得分")
    private BigDecimal performanceScore;
    /**
     * ç»©æ•ˆå·¥èµ„
     */
    @ApiModelProperty("绩效工资")
//    @Excel(name = "绩效工资")
    private BigDecimal performancePay;
    /**
     * åº”发合计
     */
    @ApiModelProperty("应发合计")
//    @Excel(name = "应发合计")
    private BigDecimal payableWages;
    @ApiModelProperty("其他收入")
    @Excel(name = "其他收入")
    private BigDecimal otherIncome;
    /**
     * ç¤¾ä¿ä¸ªäºº
     */
    @ApiModelProperty("社保个人")
//    @Excel(name = "社保个人")
    @Excel(name = "社保个人")
    private BigDecimal socialSecurityIndividuals;
    /**
     * ç¤¾ä¿å…¬å¸
     */
    @ApiModelProperty("社保公司")
//    @Excel(name = "社保公司")
    private BigDecimal socialSecurityCompanies;
    /**
     * ç¤¾ä¿åˆè®¡
     */
    @ApiModelProperty("社保合计")
//    @Excel(name = "社保合计")
    private BigDecimal socialSecurityTotal;
    /**
     * å…¬ç§¯é‡‘合计
     */
    @ApiModelProperty("公积金合计")
//    @Excel(name = "公积金合计")
    private BigDecimal providentFundTotal;
    /**
     * å…¬ç§¯é‡‘公司
     */
    @ApiModelProperty("公积金公司")
//    @Excel(name = "公积金公司")
    private BigDecimal providentFundCompany;
    /**
     * å…¬ç§¯é‡‘个人
     */
    @ApiModelProperty("公积金个人")
//    @Excel(name = "公积金个人")
    @Excel(name = "公积金个人")
    private BigDecimal providentFundIndividuals;
    /**
     * åº”税工资
     * å·¥èµ„个税
     */
    @ApiModelProperty("应税工资")
//    @Excel(name = "应税工资")
    private BigDecimal taxableWaget;
    /**
     * ä¸ªäººæ‰€å¾—税
     */
    @ApiModelProperty("个人所得税")
//    @Excel(name = "个人所得税")
    @ApiModelProperty("工资个税")
    @Excel(name = "工资个税")
    private BigDecimal personalIncomeTax;
    /**
     * å…¶ä»–支出
     */
    @ApiModelProperty("其他支出")
    @Excel(name = "其他支出")
    private BigDecimal otherDeductions;
    /**
     * åº”发工资
     */
    @ApiModelProperty("应发工资")
    @Excel(name = "应发工资")
    private BigDecimal payableWages;
    /**
     * åº”扣工资
     */
    @ApiModelProperty("应扣工资")
    @Excel(name = "应扣工资")
    private BigDecimal deductibleWages;
    /**
     * å®žå‘工资
     */
    @ApiModelProperty("实发工资")
    @Excel(name = "实发工资", width = 30)
    @Excel(name = "实发工资")
    private BigDecimal actualWages;
    /**
     * å¤‡æ³¨
     */
    @ApiModelProperty("备注")
    @Excel(name = "备注")
    private String remark;
    /**
     * ç§Ÿæˆ·ID
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    /**
     * åˆ›å»ºè€…
@@ -206,6 +169,7 @@
     * åˆ›å»ºæ—¶é—´
     */
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime createTime;
    /**
@@ -218,12 +182,7 @@
     * ä¿®æ”¹æ—¶é—´
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime updateTime;
    /**
     * ç§Ÿæˆ·ID
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
}
}
src/main/java/com/ruoyi/compensationperformance/service/CompensationPerformanceService.java
@@ -14,9 +14,11 @@
    /**
     * åˆ†é¡µæŸ¥è¯¢
     *
     * @param page
     * @param compensationPerformance
     * @param page       åˆ†é¡µæ’ä»¶
     * @param staffName  å‘˜å·¥å§“名
     * @param payDateStr è–ªèµ„日期
     * @return
     */
    IPage<CompensationPerformance> listPage(Page page, CompensationPerformance compensationPerformance);
    IPage<CompensationPerformance> listPage(Page page, String staffName, String payDateStr);
}
src/main/java/com/ruoyi/compensationperformance/service/impl/CompensationPerformanceServiceImpl.java
@@ -23,8 +23,8 @@
    @Override
    public IPage<CompensationPerformance> listPage(Page page, CompensationPerformance compensationPerformance) {
        IPage<CompensationPerformance> compensationPerformanceIPage = compensationPerformanceMapper.listPage(page, compensationPerformance);
        return compensationPerformanceIPage;
    public IPage<CompensationPerformance> listPage(Page page, String staffName, String payDateStr) {
        return compensationPerformanceMapper.listPage(page, staffName, payDateStr);
    }
}
src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java
@@ -2,6 +2,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.account.mapper.AccountIncomeMapper;
import com.ruoyi.account.pojo.AccountExpense;
@@ -330,10 +331,10 @@
            // å½“月的结束日期(每月最后一天)
            LocalDate monthEnd = currentMonth.withDayOfMonth(currentMonth.lengthOfMonth());
            // æž„建当月的查询条件(如果想一次性查全4个月数据再内存筛选,可优化为先查全再循环筛选)
            // æž„建当月的查询条件
            LambdaQueryWrapper<QualityInspect> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.ge(QualityInspect::getCheckTime, monthStart)
                    .le(QualityInspect::getCheckTime, monthEnd); // ç­›é€‰å½“月数据
            queryWrapper.ge(QualityInspect::getCheckTime, monthStart.toString())
                    .le(QualityInspect::getCheckTime, monthEnd.toString());
            List<QualityInspect> monthInspects = qualityStatisticsMapper.selectList(queryWrapper);
            BigDecimal reduce = monthInspects.stream()
                    .filter(inspect -> inspect.getInspectType().equals(0))
@@ -1916,10 +1917,8 @@
        LocalDate startDate = range[0];
        LocalDate endDate = range[1];
        String startStr = startDate.atStartOfDay()
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        String endStr = endDate.atTime(LocalTime.MAX)
                .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        String startStr = startDate.toString();
        String endStr = endDate.toString();
        List<QualityInspect> list = qualityInspectMapper.selectList(
                new LambdaQueryWrapper<QualityInspect>()
@@ -2009,20 +2008,14 @@
    @Override
    public QualityInspectionCountDto qualityInspectionCount() {
        // èŽ·å–ä»Šå¤©çš„å¼€å§‹å’Œç»“æŸæ—¥æœŸ,包含时分秒
        LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
        LocalDateTime todayEnd = LocalDateTime.now().withHour(23).withMinute(59).withSecond(59).withNano(0);
        // èŽ·å–å‰ä¸€å¤©çš„å¼€å§‹å’Œç»“æŸæ—¥æœŸ,包含时分秒
        LocalDateTime prevStart = todayStart.minusDays(1);
        LocalDateTime prevEnd = todayEnd.minusDays(1);
        String todayStr = LocalDate.now().toString();
        String prevDayStr = LocalDate.now().minusDays(1).toString();
        // æŸ¥è¯¢å‡ºæˆªæ­¢ä»Šæ—¥çš„æ€»æ£€éªŒæ•°
        List<QualityInspect> todayList = qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
                // .eq(QualityInspect::getInspectState, 1)
                .le(QualityInspect::getCheckTime, todayEnd));
                .le(QualityInspect::getCheckTime, todayStr));
        // æŸ¥è¯¢å‡ºæˆªæ­¢å‰ä¸€å¤©çš„æ€»æ£€éªŒæ•°
        List<QualityInspect> prevList = qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
                // .eq(QualityInspect::getInspectState, 1)
                .le(QualityInspect::getCheckTime, prevEnd));
                .le(QualityInspect::getCheckTime, prevDayStr));
        // è®¡ç®—今日的总检验数
        BigDecimal todayCount = todayList.stream()
                .map(QualityInspect::getQuantity)
@@ -2038,14 +2031,12 @@
        // è®¡ç®—今天的待完成数量
        List<QualityInspect> todayPendingList = qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
                .eq(QualityInspect::getInspectState, 0)
                .ge(QualityInspect::getCheckTime, todayStart)
                .le(QualityInspect::getCheckTime, todayEnd));
                .eq(QualityInspect::getCheckTime, todayStr));
        // è®¡ç®—前一天的待完成数量
        List<QualityInspect> prevPendingList = qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
                .eq(QualityInspect::getInspectState, 0)
                .ge(QualityInspect::getCheckTime, prevStart)
                .le(QualityInspect::getCheckTime, prevEnd));
                .eq(QualityInspect::getCheckTime, prevDayStr));
        // è®¡ç®—今天的待完成数量
        BigDecimal todayPendingCount = todayPendingList.stream()
                .map(QualityInspect::getQuantity)
@@ -2061,14 +2052,12 @@
        List<QualityInspect> todayCompletedList = qualityInspectMapper
                .selectList(new LambdaQueryWrapper<QualityInspect>()
                        .eq(QualityInspect::getInspectState, 1)
                        .ge(QualityInspect::getCheckTime, todayStart)
                        .le(QualityInspect::getCheckTime, todayEnd));
                        .eq(QualityInspect::getCheckTime, todayStr));
        // è®¡ç®—前一天的已完成数量
        List<QualityInspect> prevCompletedList = qualityInspectMapper
                .selectList(new LambdaQueryWrapper<QualityInspect>()
                        .eq(QualityInspect::getInspectState, 1)
                        .ge(QualityInspect::getCheckTime, prevStart)
                        .le(QualityInspect::getCheckTime, prevEnd));
                        .eq(QualityInspect::getCheckTime, prevDayStr));
        // è®¡ç®—今天的已完成数量
        BigDecimal todayCompletedCount = todayCompletedList.stream()
                .map(QualityInspect::getQuantity)
@@ -2101,18 +2090,16 @@
    @Override
    public NonComplianceWarningDto nonComplianceWarning() {
        // è¿‘七天时间区间
        LocalDateTime[] range = lastSevenDaysRange();
        LocalDateTime startTime = range[0];
        LocalDateTime endTime = range[1];
        String[] range = lastSevenDaysDateRange();
        String startStr = range[0];
        String endStr = range[1];
        // æŸ¥è¯¢è¿‘七天已处理不合格数据
        List<QualityUnqualified> list = qualityUnqualifiedMapper.selectList(
                new LambdaQueryWrapper<QualityUnqualified>()
                        .eq(QualityUnqualified::getInspectState, 1)
                        .ge(QualityUnqualified::getCheckTime, startTime)
                        .le(QualityUnqualified::getCheckTime, endTime));
                        .ge(QualityUnqualified::getCheckTime, startStr)
                        .le(QualityUnqualified::getCheckTime, endStr));
        NonComplianceWarningDto dto = new NonComplianceWarningDto();
@@ -2211,29 +2198,24 @@
    }
    /**
     * èŽ·å–è¿‘ä¸ƒå¤©çš„æ—¶é—´åŒºé—´ï¼ˆåŒ…å«ä»Šå¤©ï¼‰
     * èŽ·å–è¿‘ä¸ƒå¤©çš„æ—¥æœŸåŒºé—´ï¼ˆä»…å«å¹´æœˆæ—¥ï¼‰
     */
    public static LocalDateTime[] lastSevenDaysRange() {
    public static String[] lastSevenDaysDateRange() {
        LocalDate today = LocalDate.now();
        LocalDateTime startTime = today.minusDays(6).atStartOfDay();
        LocalDateTime endTime = today.atTime(23, 59, 59);
        return new LocalDateTime[] { startTime, endTime };
        return new String[] { today.minusDays(6).toString(), today.toString() };
    }
    @Override
    public List<CompletedInspectionCountDto> completedInspectionCount() {
        // è¿‘七天时间区间
        LocalDateTime[] range = lastSevenDaysRange();
        LocalDateTime startTime = range[0];
        LocalDateTime endTime = range[1];
        String[] range = lastSevenDaysDateRange();
        String startStr = range[0];
        String endStr = range[1];
        // æŸ¥è¯¢è¿‘七天已完成的检验数据
        List<QualityInspect> list = qualityInspectMapper.selectList(new LambdaQueryWrapper<QualityInspect>()
                .eq(QualityInspect::getInspectState, 1)
                .ge(QualityInspect::getCheckTime, startTime)
                .le(QualityInspect::getCheckTime, endTime));
                .ge(QualityInspect::getCheckTime, startStr)
                .le(QualityInspect::getCheckTime, endStr));
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM-dd");
@@ -2440,8 +2422,8 @@
        List<QualityInspect> qualityInspectList = qualityInspectMapper
                .selectList(new LambdaQueryWrapper<QualityInspect>()
                        .ge(QualityInspect::getCheckTime, startDate)
                        .le(QualityInspect::getCheckTime, endDate)
                        .ge(QualityInspect::getCheckTime, startDate.toString())
                        .le(QualityInspect::getCheckTime, endDate.toString())
                        .eq(QualityInspect::getInspectState, 1));
        QualityStatisticsDto dto = new QualityStatisticsDto();
@@ -2449,6 +2431,13 @@
        dto.setProcessNum(sumQuantity(qualityInspectList, 1)); // è¿‡ç¨‹
        dto.setFactoryNum(sumQuantity(qualityInspectList, 2)); // å‡ºåŽ‚
        // å‡è®¾ qualityInspectList æ˜¯ä¸€ä¸ª List<QualityInspect> ç±»åž‹çš„集合
        Map<String, List<QualityInspect>> groupedByCheckResult = qualityInspectList.stream()
                .collect(Collectors.groupingBy(QualityInspect::getCheckResult));
        List<QualityInspect> qualityInspects = groupedByCheckResult.get("不合格");
        if(ObjectUtils.isNull(qualityInspects) || qualityInspects.size()==0){
            return null;
        }
        // 4. å¤„理图表项 (Item)
        List<QualityStatisticsItem> itemList = new ArrayList<>();
@@ -2458,7 +2447,7 @@
                        i -> i.getCheckTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()));
        if (type == 3) {
            // å­£åº¦æ¨¡å¼ï¼šæŒ‰æœˆåˆ†ç»„
            Map<String, List<QualityInspect>> groupByMonth = qualityInspectList.stream()
            Map<String, List<QualityInspect>> groupByMonth = qualityInspects.stream()
                    .collect(Collectors.groupingBy(i -> {
                        LocalDate ld = dateMap.get(i);
                        return ld.format(DateTimeFormatter.ofPattern("yyyy-MM"));
@@ -2471,7 +2460,7 @@
            }
        } else {
            // å‘¨æˆ–月模式:按天分组
            Map<String, List<QualityInspect>> groupByDay = qualityInspectList.stream()
            Map<String, List<QualityInspect>> groupByDay = qualityInspects.stream()
                    .collect(Collectors.groupingBy(i -> {
                        LocalDate ld = dateMap.get(i);
                        return ld.format(DateTimeFormatter.ofPattern("MM/dd"));
@@ -2543,4 +2532,4 @@
        return productProcessMapper.calculateProductionStatistics(startDateTime, endDateTime, userId, processIds);
    }
}
}
src/main/java/com/ruoyi/project/system/controller/SysUserClientController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package com.ruoyi.project.system.controller;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.project.system.domain.GetuiConfig;
import com.ruoyi.project.system.domain.SysUserClient;
import com.ruoyi.project.system.service.SysUserClientService;
import com.ruoyi.common.utils.SecurityUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * ç”¨æˆ·å®‰å“设备管理控制层
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/2/9
 */
@Api(tags = "用户设备绑定")
@RestController
@RequestMapping("/system/client")
public class SysUserClientController extends BaseController {
    @Autowired
    private SysUserClientService sysUserClientService;
    /**
     * æ·»åŠ /更新用户cid
     */
    @PostMapping("/addOrUpdateClientId")
    @ApiOperation("添加/更新用户cid")
    public AjaxResult addOrUpdateClientId(@RequestBody SysUserClient sysUserClient) {
        Long userId = SecurityUtils.getUserId();
        sysUserClient.setUserId(userId);
        boolean result = sysUserClientService.addOrUpdateClientId(sysUserClient);
        return result ? success() : error("设备绑定失败");
    }
}
src/main/java/com/ruoyi/project/system/domain/GetuiConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package com.ruoyi.project.system.domain;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * <p>
 * ä¸ªæŽ¨ (Unipush v2) æ¶ˆæ¯æŽ¨é€é…ç½®ç±»
 * </p>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/2/9
 */
@Data
@Component
@ConfigurationProperties(prefix = "ruoyi.getui")
public class GetuiConfig {
    /**
     * AppID
     */
    private String appId;
    /**
     * AppKey
     */
    private String appKey;
    /**
     * MasterSecret
     */
    private String masterSecret;
    /**
     * ä¸ªæŽ¨ RESTful API åŸŸååœ°å€
     */
    private String domain;
    /**
     * ç¦»çº¿æŽ¨é€ Intent ç›®æ ‡ç»„件名
     * æ ¼å¼: åŒ…名/入口Activity名
     */
    private String intentComponent;
}
src/main/java/com/ruoyi/project/system/domain/SysUserClient.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
package com.ruoyi.project.system.domain;
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 lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
 * <br>
 * ç”¨æˆ·å®‰å“设备关联对象 sys_user_client
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/2/9
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@TableName("sys_user_client")
public class SysUserClient implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * ç”¨æˆ·ID
     */
    @TableId(type = IdType.INPUT)
    private Long userId;
    /**
     * ä¸ªæŽ¨è®¾å¤‡æ ‡è¯† (CID)
     */
    private String cid;
    /**
     * æœ€åŽæ´»è·ƒæ—¶é—´
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;
}
src/main/java/com/ruoyi/project/system/mapper/SysMenuMapper.java
@@ -122,4 +122,12 @@
     * @return ç»“æžœ
     */
    public SysMenu checkMenuNameUnique(@Param("menuName") String menuName, @Param("parentId") Long parentId);
    /**
     * æ ¹æ®è·¯ç”±åœ°å€æŸ¥è¯¢èœå•
     *
     * @param lastSegment è·¯ç”±åœ°å€
     * @return èœå•
     */
    SysMenu selectMenuByPath(String lastSegment);
}
src/main/java/com/ruoyi/project/system/mapper/SysUserClientMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.project.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.project.system.domain.SysUserClient;
/**
 * <br>
 * ç”¨æˆ·å®‰å“设备关联mapper
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/2/9
 */
public interface SysUserClientMapper extends BaseMapper<SysUserClient> {
}
src/main/java/com/ruoyi/project/system/service/SysUserClientService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,19 @@
package com.ruoyi.project.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.project.system.domain.SysUserClient;
/**
 * <br>
 * ç”¨æˆ·å®‰å“设备关联接口
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/2/9
 */
public interface SysUserClientService extends IService<SysUserClient> {
    boolean addOrUpdateClientId(SysUserClient sysUserClient);
}
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java
@@ -53,6 +53,9 @@
    @Lazy
    private ISysNoticeService sysNoticeService;
    @Autowired
    private UnipushService unipushService;
    /**
     * æŸ¥è¯¢å…¬å‘Šä¿¡æ¯
     *
@@ -146,6 +149,11 @@
        Long tenantId = SecurityUtils.getLoginUser().getTenantId();
        List<SysNotice> sysNotices = consigneeId.stream().map(it -> convertSysNotice(title, message, it,tenantId, jumpPath, userId)).collect(Collectors.toList());
        sysNoticeService.saveBatch(sysNotices);
        try {
            unipushService.sendClientMessage(sysNotices);
        } catch (Exception e) {
            log.error("APP推送通知失败,原因: {}", e);
        }
    }
    @Override
@@ -197,6 +205,11 @@
                .collect(Collectors.toList());
        sysNoticeService.saveBatch(collect);
        try {
            unipushService.sendClientMessage(collect);
        } catch (Exception e) {
            log.error("APP推送通知失败,原因: {}", e);
        }
    }
src/main/java/com/ruoyi/project/system/service/impl/SysUserClientServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
package com.ruoyi.project.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.project.system.domain.SysUserClient;
import com.ruoyi.project.system.mapper.SysUserClientMapper;
import com.ruoyi.project.system.service.SysUserClientService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
/**
 * ç”¨æˆ·å®‰å“设备关联接口实现类
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/2/9
 */
@Service
public class SysUserClientServiceImpl extends ServiceImpl<SysUserClientMapper, SysUserClient> implements SysUserClientService {
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean addOrUpdateClientId(SysUserClient sysUserClient) {
        if (sysUserClient == null || sysUserClient.getUserId() == null || StringUtils.isEmpty(sysUserClient.getCid())) {
            return false;
        }
        String cid = sysUserClient.getCid();
        Long userId = sysUserClient.getUserId();
        remove(new LambdaQueryWrapper<SysUserClient>().eq(SysUserClient::getCid, cid).ne(SysUserClient::getUserId, userId));
        SysUserClient userClient = new SysUserClient();
        userClient.setUserId(userId);
        userClient.setCid(cid);
        userClient.setUpdateTime(new Date());
        return saveOrUpdate(userClient);
    }
}
src/main/java/com/ruoyi/project/system/service/impl/UnipushService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,200 @@
package com.ruoyi.project.system.service.impl;
import com.getui.push.v2.sdk.ApiHelper;
import com.getui.push.v2.sdk.GtApiConfiguration;
import com.getui.push.v2.sdk.api.PushApi;
import com.getui.push.v2.sdk.common.ApiResult;
import com.getui.push.v2.sdk.dto.req.Audience;
import com.getui.push.v2.sdk.dto.req.message.PushChannel;
import com.getui.push.v2.sdk.dto.req.message.PushDTO;
import com.getui.push.v2.sdk.dto.req.message.PushMessage;
import com.getui.push.v2.sdk.dto.req.message.android.AndroidDTO;
import com.getui.push.v2.sdk.dto.req.message.android.ThirdNotification;
import com.getui.push.v2.sdk.dto.req.message.android.Ups;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.project.system.domain.GetuiConfig;
import com.ruoyi.project.system.domain.SysMenu;
import com.ruoyi.project.system.domain.SysNotice;
import com.ruoyi.project.system.domain.SysUserClient;
import com.ruoyi.project.system.mapper.SysMenuMapper;
import com.ruoyi.project.system.service.SysUserClientService;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
import java.util.Map;
/**
 * APP消息推送服务
 *
 * @author deslrey
 * @version 1.4
 * @since 2026/2/9
 */
@Slf4j
@Component
public class UnipushService {
    @Autowired
    private SysMenuMapper sysMenuMapper;
    @Autowired
    private GetuiConfig getuiConfig;
    @Autowired
    private SysUserClientService userClientService;
    private PushApi pushApi;
    private static final String DEFAULT_APP_PAGE = "pages/index";
    @PostConstruct
    public void init() {
        GtApiConfiguration config = new GtApiConfiguration();
        config.setAppId(getuiConfig.getAppId());
        config.setAppKey(getuiConfig.getAppKey());
        config.setMasterSecret(getuiConfig.getMasterSecret());
        config.setDomain(getuiConfig.getDomain());
        ApiHelper apiHelper = ApiHelper.build(config);
        this.pushApi = apiHelper.creatApi(PushApi.class);
    }
    /**
     * æ‰¹é‡å‘送通知公告到移动端
     */
    @Async
    public void sendClientMessage(List<SysNotice> sysNoticeList) {
        if (sysNoticeList == null || sysNoticeList.isEmpty()) {
            return;
        }
        for (SysNotice sysNotice : sysNoticeList) {
            SysUserClient client = userClientService.getById(sysNotice.getConsigneeId());
            if (client == null || StringUtils.isEmpty(client.getCid())) {
                log.warn("用户 {} æœªç»‘定移动端 CID,跳过推送", sysNotice.getConsigneeId());
                continue;
            }
            // è½¬æ¢è·¯å¾„
            String appPath = convertWebPathToAppPath(sysNotice.getJumpPath());
            // æŽ¨é€
            sendRoutingPush(
                    client.getCid(),
                    sysNotice.getNoticeTitle(),
                    sysNotice.getRemark() != null ? sysNotice.getRemark() : sysNotice.getNoticeContent(),
                    appPath
            );
        }
    }
    /**
     * å°† Web ç«¯åˆ†å±‚全路径转换为 App ç«¯ç»„件路由
     */
    private String convertWebPathToAppPath(String webPath) {
        if (StringUtils.isEmpty(webPath)) {
            return DEFAULT_APP_PAGE;
        }
        String pathOnly = webPath;
        String queryString = "";
        if (webPath.contains("?")) {
            int index = webPath.indexOf("?");
            pathOnly = webPath.substring(0, index);
            queryString = webPath.substring(index);
        }
        String lastSegment;
        int lastSlashIndex = pathOnly.lastIndexOf("/");
        if (lastSlashIndex != -1) {
            lastSegment = pathOnly.substring(lastSlashIndex + 1);
        } else {
            lastSegment = pathOnly;
        }
        if (StringUtils.isEmpty(lastSegment)) {
            return DEFAULT_APP_PAGE;
        }
        SysMenu menu = sysMenuMapper.selectMenuByPath(lastSegment);
        if (menu != null && StringUtils.isNotEmpty(menu.getAppComponent())) {
            String appPath = menu.getAppComponent();
            if (appPath.startsWith("/")) {
                appPath = appPath.substring(1);
            }
            //  æ‹¼æŽ¥ Web ç«¯åŽŸå§‹å‚æ•°å¹¶è¿”å›ž
            return appPath + queryString;
        }
        return DEFAULT_APP_PAGE;
    }
    /**
     * å‘送单人路由推送
     */
    private void sendRoutingPush(String cid, String title, String content, String targetPath) {
        log.info("准备推送消息: CID={}, Title={}, TargetPath={}", cid, title, targetPath);
        PushDTO<Audience> pushDTO = new PushDTO<>();
        pushDTO.setRequestId("REQ_" + System.currentTimeMillis());
        // åœ¨çº¿é€ä¼ å†…容
        PushMessage pushMessage = new PushMessage();
        String transmissionContent = String.format(
                "{\"title\":\"%s\",\"content\":\"%s\",\"payload\":\"%s\"}",
                title, content, targetPath
        );
        pushMessage.setTransmission(transmissionContent);
        pushDTO.setPushMessage(pushMessage);
        // æŽ¥æ”¶äºº
        Audience audience = new Audience();
        audience.addCid(cid);
        pushDTO.setAudience(audience);
        // ç¦»çº¿æŽ¨é€é€šé“
        pushDTO.setPushChannel(getPushChannel(title, content, targetPath));
        try {
            ApiResult<Map<String, Map<String, String>>> result = pushApi.pushToSingleByCid(pushDTO);
            if (result.isSuccess()) {
                log.info("Unipush æŽ¨é€æˆåŠŸ: CID={}", cid);
            } else {
                log.error("Unipush æŽ¨é€å¤±è´¥: CID={}, Code={}, Msg={}", cid, result.getCode(), result.getMsg());
            }
        } catch (Exception e) {
            log.error("Unipush æŽ¨é€å¼‚常: ", e);
        }
    }
    @NotNull
    private PushChannel getPushChannel(String title, String content, String targetPath) {
        PushChannel pushChannel = new PushChannel();
        AndroidDTO androidDTO = new AndroidDTO();
        Ups ups = new Ups();
        ThirdNotification thirdNotification = new ThirdNotification();
        thirdNotification.setTitle(title);
        thirdNotification.setBody(content);
        thirdNotification.setClickType("intent");
        String intent = "intent:#Intent;launchFlags=0x04000000;"
                + "component=" + getuiConfig.getIntentComponent() + ";"
                + "S.UP-OL-P9=true;"
                + "S.path=" + targetPath + ";"
                + "S.payload=" + targetPath + ";"
                + "end";
        thirdNotification.setIntent(intent);
        ups.setNotification(thirdNotification);
        androidDTO.setUps(ups);
        pushChannel.setAndroid(androidDTO);
        return pushChannel;
    }
}
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java
@@ -209,15 +209,15 @@
        if (productList != null && !productList.isEmpty()) {
            handleSalesLedgerProducts(purchaseLedger.getId(), productList, purchaseLedgerDto.getType());
        }
        //新增原材料检验
        if (productList != null) {
            for (SalesLedgerProduct saleProduct : productList) {
                //是否推送质检,如果true就添加
                if (saleProduct.getIsChecked()) {
                    addQualityInspect(purchaseLedger, saleProduct);
                }
            }
        }
        //新增原材料检验  å®¡æ‰¹ä¹‹åŽæ‰ç”Ÿæˆæ£€éªŒ
//        if (productList != null) {
//            for (SalesLedgerProduct saleProduct : productList) {
//                //是否推送质检,如果true就添加
//                if (saleProduct.getIsChecked()) {
//                    addQualityInspect(purchaseLedger, saleProduct);
//                }
//            }
//        }
        // 5. è¿ç§»ä¸´æ—¶æ–‡ä»¶åˆ°æ­£å¼ç›®å½•
        if (purchaseLedgerDto.getTempFileIds() != null && !purchaseLedgerDto.getTempFileIds().isEmpty()) {
            migrateTempFilesToFormal(purchaseLedger.getId(), purchaseLedgerDto.getTempFileIds());
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java
@@ -55,7 +55,7 @@
     */
    @GetMapping("/{id}")
    public AjaxResult QualityUnqualifiedDetail(@PathVariable("id") Integer id) {
        return AjaxResult.success(qualityUnqualifiedService.getById(id));
        return AjaxResult.success(qualityUnqualifiedService.getUnqualified(id));
    }
    /**
src/main/java/com/ruoyi/quality/mapper/QualityUnqualifiedMapper.java
@@ -17,4 +17,6 @@
    IPage<QualityUnqualified> qualityUnqualifiedListPage(Page page, @Param("qualityUnqualified") QualityUnqualified qualityUnqualified);
    List<QualityUnqualified> qualityUnqualifiedExport(@Param("qualityUnqualified") QualityUnqualified qualityUnqualified);
    QualityUnqualified getUnqualified(@Param("id") Integer id);
}
src/main/java/com/ruoyi/quality/pojo/QualityUnqualified.java
@@ -135,4 +135,9 @@
    @ApiModelProperty("关联检测id")
    private Long inspectId;
    @ApiModelProperty("是否不合格处理自己新增")
    @TableField(exist = false)
    private Boolean method;
}
src/main/java/com/ruoyi/quality/service/IQualityUnqualifiedService.java
@@ -17,4 +17,5 @@
    int deal(QualityUnqualified qualityUnqualified);
    QualityUnqualified getUnqualified(Integer id);
}
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
@@ -64,7 +64,7 @@
    public int deal(QualityUnqualified qualityUnqualified) {
        QualityUnqualified unqualified = qualityUnqualifiedMapper.selectById(qualityUnqualified.getId());
        QualityInspect qualityInspect = qualityInspectService.getById(unqualified.getInspectId());
        if (ObjectUtils.isNotNull(qualityInspect) && qualityInspect.getInspectType()!=0) {
        if (ObjectUtils.isNotNull(qualityInspect) && qualityInspect.getInspectType() != 0) {
            switch (qualityUnqualified.getDealResult()) {
                case "返修":
                case "返工":
@@ -113,7 +113,7 @@
                                    }
                                }
                                // ç”Ÿæˆå®Œæ•´çš„工单号
                                String workOrderNoStr ="FG" +String.format("%s%03d", datePrefix, sequenceNumber);
                                String workOrderNoStr = "FG" + String.format("%s%03d", datePrefix, sequenceNumber);
                                ProductWorkOrder productWorkOrder = new ProductWorkOrder();
                                productWorkOrder.setProductProcessRouteItemId(productProcessRouteItem.getId());
                                productWorkOrder.setProductOrderId(order.getId());
@@ -138,8 +138,27 @@
                default:
                    break;
            }
        } else {
            switch (qualityUnqualified.getDealResult()) {
                case "报废":
                    //调用不合格库存接口 å…¥ä¸åˆæ ¼åº“
                    stockUtils.addUnStock(Long.valueOf(unqualified.getModel()), unqualified.getQuantity(), StockInUnQualifiedRecordTypeEnum.DEFECTIVE_SCRAP.getCode(), unqualified.getId());
                    break;
                case "让步放行":
                    //调用提交合格的接口
                    stockUtils.addStock(Long.valueOf(unqualified.getModel()), unqualified.getQuantity(), StockInQualifiedRecordTypeEnum.DEFECTIVE_PASS.getCode(), unqualified.getId());
                    break;
                default:
                    break;
            }
        }
        qualityUnqualified.setInspectState(1);//已处理
        return qualityUnqualifiedMapper.updateById(qualityUnqualified);
    }
    @Override
    public QualityUnqualified getUnqualified(Integer id) {
        return qualityUnqualifiedMapper.getUnqualified(id);
    }
}
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/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/dto/PersonalAttendanceRecordsDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package com.ruoyi.staff.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import lombok.Data;
@Data
public class PersonalAttendanceRecordsDto extends PersonalAttendanceRecords {
    private String staffName;
    private String staffNo;
    private String deptName;
    private Long deptId;
}
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceRecordsMapper.java
@@ -1,9 +1,24 @@
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.dto.StaffOnJobDto;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.staff.pojo.StaffOnJob;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
 * <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);
}
src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java
@@ -20,6 +20,7 @@
    /**
     * ç»Ÿè®¡æŒ‡å®šæ—¥æœŸçš„在职员工数
     *
     * @param date æ—¥æœŸ
     * @return åœ¨èŒå‘˜å·¥æ•°
     */
@@ -27,9 +28,18 @@
    /**
     * ç»Ÿè®¡æŒ‡å®šæœˆä»½çš„æ–°å…¥èŒå‘˜å·¥æ•°
     *
     * @param monthStart æœˆä»½å¼€å§‹æ—¥æœŸ
     * @param monthEnd æœˆä»½ç»“束日期
     * @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/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/pojo/StaffOnJob.java
@@ -66,7 +66,7 @@
     /**
     * éƒ¨é—¨
     */
    private Integer sysDeptId;
    private Long sysDeptId;
    /**
     * å®¶åº­ä½å€
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/application-dev.yml
@@ -15,6 +15,16 @@
  captchaType: math
  # ååŒå®¡æ‰¹ç¼–号前缀(配置文件后缀命名)
  approvalNumberPrefix: DEV
  # ä¸ªæŽ¨ Unipush é…ç½®
  getui:
    appId: PfjyAAE0FK64FaO1w2CMb1
    appKey: zTMb831OEL6J4GK1uE3Ob4
    masterSecret: K1GFtsv42v61tXGnF7SGE5
    domain: https://restapi.getui.cn/v2/
    # ç¦»çº¿æŽ¨é€ä½¿ç”¨çš„包名/组件名
    intentComponent: uni.app.UNI099A590/io.dcloud.PandoraEntry
# å¼€å‘环境配置
server:
  # æœåŠ¡å™¨çš„HTTP端口,默认为8080
src/main/resources/application-new.yml
@@ -15,6 +15,16 @@
  captchaType: math
  # ååŒå®¡æ‰¹ç¼–号前缀(配置文件后缀命名)
  approvalNumberPrefix: NEW
  # ä¸ªæŽ¨ Unipush é…ç½®
  getui:
    appId: PfjyAAE0FK64FaO1w2CMb1
    appKey: zTMb831OEL6J4GK1uE3Ob4
    masterSecret: K1GFtsv42v61tXGnF7SGE5
    domain: https://restapi.getui.cn/v2/
    # ç¦»çº¿æŽ¨é€ä½¿ç”¨çš„包名/组件名
    intentComponent: uni.app.UNI099A590/io.dcloud.PandoraEntry
# å¼€å‘环境配置
server:
  # æœåŠ¡å™¨çš„HTTP端口,默认为8080
src/main/resources/mapper/compensationperformance/CompensationPerformanceMapper.xml
@@ -3,14 +3,23 @@
<mapper namespace="com.ruoyi.compensationperformance.mapper.CompensationPerformanceMapper">
    <select id="listPage" resultType="com.ruoyi.compensationperformance.pojo.CompensationPerformance">
        select * from compensation_performance
        SELECT
        cp.*,
        soj.staff_name AS staffName,
        sp.post_name AS postName,
        sd.dept_name AS deptName
        FROM compensation_performance cp
        LEFT JOIN staff_on_job soj ON soj.id = cp.staff_id
        LEFT JOIN sys_post sp ON sp.post_id = soj.sys_post_id
        LEFT JOIN sys_dept sd ON sd.dept_id = soj.sys_dept_id
        <where>
            <if test="req.name != null and req.name != ''">
                and `name` like concat('%',#{req.name},'%')
            <if test="staffName != null and staffName != ''">
                AND soj.staff_name LIKE CONCAT('%', #{staffName}, '%')
            </if>
            <if test="req.payDateStr != null and req.payDateStr != ''">
                and pay_date like concat('%',#{req.payDateStr},'%')
            <if test="payDateStr != null and payDateStr != ''">
                AND DATE_FORMAT(cp.pay_date, '%Y-%m') = #{payDateStr}
            </if>
        </where>
        ORDER BY cp.pay_date DESC, cp.id DESC
    </select>
</mapper>
src/main/resources/mapper/production/ProductProcessMapper.xml
@@ -21,14 +21,15 @@
    <select id="calculateProductionStatistics" resultType="com.ruoyi.home.dto.processDataProductionStatisticsDto">
        SELECT
        pp.name AS processName,
        SUM((ppo.quantity + ppo.scrap_qty) * pp.salary_quota) AS totalInput,
        SUM(ppo.scrap_qty * pp.salary_quota) AS totalScrap,
        SUM(ppo.quantity * pp.salary_quota) AS totalOutput
        SUM(pi.quantity) AS totalInput,
        SUM(distinct ppo.scrap_qty) AS totalScrap,
        SUM(distinct (ppo.quantity - ppo.scrap_qty)) AS totalOutput
        FROM
        production_product_output ppo
        INNER JOIN production_product_main ppm ON ppo.product_main_id = ppm.id
        INNER JOIN product_process_route_item ppri ON ppm.product_process_route_item_id = ppri.id
        INNER JOIN product_process pp ON ppri.process_id = pp.id
        INNER JOIN production_product_input pi ON pi.product_main_id = ppm.id
        <where>
            <if test="startDateTime != null">
                AND ppo.create_time &gt;= #{startDateTime}
src/main/resources/mapper/production/ProductionProductOutputMapper.xml
@@ -49,7 +49,7 @@
    <select id="selectDailyOutputStats" resultType="java.util.Map">
        SELECT
            DATE_FORMAT(create_time, '%Y-%m-%d') as date,
            SUM(quantity) as quantity
            SUM(quantity-scrap_qty) as quantity
        FROM
            production_product_output
        WHERE
src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml
@@ -3,8 +3,29 @@
<mapper namespace="com.ruoyi.quality.mapper.QualityUnqualifiedMapper">
    <select id="qualityUnqualifiedListPage" resultType="com.ruoyi.quality.pojo.QualityUnqualified">
        SELECT
        *
        FROM quality_unqualified
        qu.id,
        qu.inspect_type,
        qu.inspect_state,
        qu.check_time,
        qu.check_name,
        qu.product_id,
        qu.product_name,
        qu.unit,
        qu.quantity,
        qu.defective_phenomena,
        qu.deal_result,
        qu.deal_name,
        qu.deal_time,
        CASE
        WHEN qu.model = pm.id THEN pm.model
        ELSE qu.model
        END AS model,
        CASE
        WHEN qu.model = pm.id THEN true
        ELSE false
        END AS method
        FROM quality_unqualified qu
        LEFT JOIN product_model pm ON qu.model = pm.id
        where
        1=1
        <if test="qualityUnqualified.inspectType != null ">
@@ -39,4 +60,33 @@
            AND product_name = #{qualityUnqualified.productName}
        </if>
    </select>
    <select id="getUnqualified" resultType="com.ruoyi.quality.pojo.QualityUnqualified">
        SELECT
            qu.id,
            qu.inspect_type,
            qu.inspect_state,
            qu.check_time,
            qu.check_name,
            qu.product_id,
            qu.product_name,
            qu.unit,
            qu.quantity,
            qu.defective_phenomena,
            qu.deal_result,
            qu.deal_name,
            qu.deal_time,
            CASE
                WHEN qu.model = pm.id THEN pm.model
                ELSE qu.model
                END AS model,
            CASE
                WHEN qu.model = pm.id THEN true
                ELSE false
                END AS method
        FROM quality_unqualified qu
                 LEFT JOIN product_model pm ON qu.model = pm.id
        where
            1=1
        and qu.id = #{id}
    </select>
</mapper>
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -8,7 +8,7 @@
        SELECT
        T1.*,
        CASE
        WHEN t2.qualitity > T1.quantity THEN 1
        WHEN t2.qualitity-t2.locked_quantity >= T1.quantity THEN 1
        ELSE 0
        END as has_sufficient_stock
        FROM
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>
src/main/resources/mapper/staff/StaffOnJobMapper.xml
@@ -13,7 +13,7 @@
        sys_dept sd ON sd.dept_id = staff_on_job.sys_dept_id
        where 1=1
        <if test="staffOnJob.staffState != null">
        AND staff_state = #{staffOnJob.staffState}
            AND staff_state = #{staffOnJob.staffState}
        </if>
        <if test="staffOnJob.staffName != null and staffOnJob.staffName != '' ">
            AND staff_name LIKE CONCAT('%',#{staffOnJob.staffName},'%')
@@ -22,7 +22,7 @@
            AND contract_expire_time &gt;= DATE_FORMAT(#{staffOnJob.entryDateStart},'%Y-%m-%d')
        </if>
        <if test="staffOnJob.entryDateEnd != null and staffOnJob.entryDateEnd != '' ">
            AND  contract_expire_time &lt;= DATE_FORMAT(#{staffOnJob.entryDateEnd},'%Y-%m-%d')
            AND contract_expire_time &lt;= DATE_FORMAT(#{staffOnJob.entryDateEnd},'%Y-%m-%d')
        </if>
    </select>
    <select id="staffOnJobList" resultType="com.ruoyi.staff.dto.StaffOnJobDto">
@@ -48,7 +48,7 @@
        SELECT COUNT(*)
        FROM staff_on_job
        WHERE staff_state = 1
        AND DATE_FORMAT(create_time, '%Y-%m-%d') &lt;= #{date}
          AND DATE_FORMAT(create_time, '%Y-%m-%d') &lt;= #{date}
    </select>
    <!-- ç»Ÿè®¡æŒ‡å®šæœˆä»½çš„æ–°å…¥èŒå‘˜å·¥æ•° -->
@@ -56,6 +56,23 @@
        SELECT COUNT(*)
        FROM staff_on_job
        WHERE staff_state = 1
        AND DATE_FORMAT(create_time, '%Y-%m-%d') BETWEEN #{monthStart} AND #{monthEnd}
          AND DATE_FORMAT(create_time, '%Y-%m-%d') BETWEEN #{monthStart} AND #{monthEnd}
    </select>
    <select id="selectStaffByNickName" resultType="com.ruoyi.staff.pojo.StaffOnJob" parameterType="java.lang.String">
        SELECT
        id
        nick_name AS staffName
        FROM staff_on_job
        WHERE del_flag = '0'
        <choose>
            <when test="staffName != null and staffName != ''">
                AND nick_name = #{staffName}
            </when>
            <otherwise>
                AND 1 = 0
            </otherwise>
        </choose>
        LIMIT 1
    </select>
</mapper>
src/main/resources/mapper/system/SysMenuMapper.xml
@@ -85,7 +85,7 @@
        where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = 0  AND ro.status = 0
        order by m.parent_id, m.order_num
    </select>
    <select id="selectMenuListByRoleId" resultType="Long">
        select m.menu_id
        from sys_menu m
@@ -133,7 +133,14 @@
        <include refid="selectMenuVo"/>
        where menu_name=#{menuName} and parent_id = #{parentId} limit 1
    </select>
    <select id="selectMenuByPath" resultType="com.ruoyi.project.system.domain.SysMenu" parameterType="java.lang.String">
        SELECT menu_id, menu_name, parent_id, path, app_component, status
        FROM sys_menu
        WHERE path = #{path}
          AND status = '0' LIMIT 1
    </select>
    <update id="updateMenu" parameterType="com.ruoyi.project.system.domain.SysMenu">
        update sys_menu
        <set>