liding
5 小时以前 1a3f5db043fd381b60a0a5f40687136274a2cbd1
Merge branch 'refs/heads/dev_New' into dev_New_kthg

# Conflicts:
# src/main/java/com/ruoyi/compensationperformance/service/impl/CompensationPerformanceServiceImpl.java
# src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java
已添加20个文件
已修改76个文件
3328 ■■■■ 文件已修改
doc/20260209_create_personal_attendance_records.sql 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 9 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/CodeGenerator.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/AccountExpense.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/pojo/AccountIncome.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/account/service/impl/BorrowInfoServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/controller/ProductController.java 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/dto/ProductModelExportDto.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/IProductModelService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java 83 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/controller/CompensationPerformanceController.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/mapper/CompensationPerformanceMapper.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/pojo/CompensationPerformance.java 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/service/CompensationPerformanceService.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/compensationperformance/service/impl/CompensationPerformanceServiceImpl.java 11 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/home/service/impl/HomeServiceImpl.java 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductOrderController.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/controller/ProductionProductMainController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java 92 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/controller/SysNoticeController.java 17 ●●●● 补丁 | 查看 | 原始文档 | 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/SysNotice.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/SysUserClient.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/domain/vo/RouterVo.java 14 ●●●●● 补丁 | 查看 | 原始文档 | 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/ISysNoticeService.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/SysUserClientService.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysMenuServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java 38 ●●●●● 补丁 | 查看 | 原始文档 | 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 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PaymentRegistrationServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/purchase/service/impl/PurchaseLedgerServiceImpl.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityInspectController.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/controller/QualityUnqualifiedController.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/mapper/QualityUnqualifiedMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | 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/QualityInspectServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerController.java 119 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/dto/StatisticsTableDto.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/InvoiceRegistrationServiceImpl.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/MetricStatisticsServiceImpl.java 74 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ReceiptPaymentServiceImpl.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/HolidayApplicationController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceLocationConfigController.java 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/controller/PersonalAttendanceRecordsController.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/dto/PersonalAttendanceRecordsDto.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceLocationConfigMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/PersonalAttendanceRecordsMapper.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/mapper/StaffOnJobMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceLocationConfig.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/PersonalAttendanceRecords.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/pojo/StaffOnJob.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/PersonalAttendanceLocationConfigService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/PersonalAttendanceRecordsService.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceLocationConfigServiceImpl.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/PersonalAttendanceRecordsServiceImpl.java 248 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/service/impl/StaffOnJobServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/task/PersonalAttendanceRecordsTask.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/staff/utils/LocationUtils.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/stock/mapper/StockInventoryMapper.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-dev.yml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-khl.yml 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-new.yml 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/application-newTest.yml 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/account/AccountIncomeMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/compensationperformance/CompensationPerformanceMapper.xml 31 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductProcessMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductInputMapper.xml 41 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductMainMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/production/ProductionProductOutputMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/purchase/PurchaseLedgerMapper.xml 44 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/quality/QualityUnqualifiedMapper.xml 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/PersonalAttendanceLocationConfigMapper.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/staff/StaffOnJobMapper.xml 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/stock/StockInventoryMapper.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysMenuMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/system/SysNoticeMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
doc/20260209_create_personal_attendance_records.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
#员工考勤表
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),
    unique idx_staff_on_job_id_date (staff_on_job_id, date)
);
pom.xml
@@ -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>
@@ -308,6 +308,13 @@
            <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>
src/main/java/com/ruoyi/CodeGenerator.java
@@ -19,11 +19,11 @@
// æ¼”示例子,执行 main æ–¹æ³•控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
    public static String database_url = "jdbc:mysql://localhost:3306/product-inventory-management-new";
    public static String database_url = "jdbc:mysql://1.15.17.182:9999/product-inventory-management-new";
    public static String database_username = "root";
    public static String database_password= "123456";
    public static String database_password= "xd@123456..";
    public static String author = "芯导软件(江苏)有限公司";
    public static String model = "purchase"; // æ¨¡å—
    public static String model = "staff"; // æ¨¡å—
    public static String setParent = "com.ruoyi."+ model; // åŒ…路径
    public static String tablePrefix = ""; // è®¾ç½®è¿‡æ»¤è¡¨å‰ç¼€
    public static void main(String[] args) {
src/main/java/com/ruoyi/account/pojo/AccountExpense.java
@@ -48,9 +48,9 @@
    private Date expenseDate;
    /**
     * æ”¯å‡ºç±»åž‹(办公用品,员工工资,差旅费,设备费用,其他)
     * æ”¯å‡ºç±»åž‹(办公用品,员工工资,差旅费,设备费用,来票付款,其他)
     */
    @Excel(name = "支出类型",readConverterExp = "0=办公用品,1=员工工资,2=差旅费,3=设备费用,4=其他")
    @Excel(name = "支出类型",readConverterExp = "0=办公用品,1=员工工资,2=差旅费,3=设备费用,4=来票付款,5=其他")
    @NotBlank(message = "支出类型不能为空!!")
    private String expenseType;
src/main/java/com/ruoyi/account/pojo/AccountIncome.java
@@ -50,7 +50,7 @@
    /**
     * æ”¶å…¥ç±»åž‹(销售收入,服务收入,其他收入)
     */
    @Excel(name = "收入类型",readConverterExp = "0=销售收入,1=服务收入,2=其他收入")
    @Excel(name = "收入类型",readConverterExp = "0=销售收入,1=服务收入,2=其他收入,3=回款收入")
    @NotBlank(message = "收入类型不能为空!!")
    private String incomeType;
src/main/java/com/ruoyi/account/service/impl/BorrowInfoServiceImpl.java
@@ -100,7 +100,7 @@
                accountExpense.setBusinessId(borrowInfo.getId());
                accountExpense.setBusinessType(2);
                accountExpense.setExpenseDate(DateUtils.toDate(borrowInfo.getRepayDate()));
                accountExpense.setExpenseType("4");
                accountExpense.setExpenseType("5");
                accountExpense.setExpenseMoney(borrowInfo.getBorrowAmount());
                accountExpense.setExpenseDescribed("还款");
                accountExpense.setExpenseMethod("3");
src/main/java/com/ruoyi/approve/service/impl/ApproveProcessServiceImpl.java
@@ -25,8 +25,12 @@
import com.ruoyi.project.system.mapper.SysDeptMapper;
import com.ruoyi.project.system.mapper.SysUserMapper;
import com.ruoyi.project.system.service.ISysNoticeService;
import com.ruoyi.purchase.mapper.PurchaseLedgerMapper;
import com.ruoyi.purchase.pojo.PurchaseLedger;
import com.ruoyi.sales.mapper.CommonFileMapper;
import com.ruoyi.sales.mapper.ShippingInfoMapper;
import com.ruoyi.sales.pojo.CommonFile;
import com.ruoyi.sales.pojo.ShippingInfo;
import com.ruoyi.sales.service.impl.CommonFileServiceImpl;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
@@ -157,15 +161,61 @@
        return sysDeptList;
    }
    @Autowired
    private PurchaseLedgerMapper purchaseLedgerMapper;
    @Autowired
    private ShippingInfoMapper shippingInfoMapper;
    @Override
    public IPage<ApproveProcess> listAll(Page page, ApproveProcess approveProcess) {
        IPage<ApproveProcess> approveProcessIPage = approveProcessMapper.listPage(page, approveProcess);
        List<ApproveProcess> records = approveProcessIPage.getRecords();
        for (ApproveProcess record : records) {
            List<CommonFile> commonFiles = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
            List<CommonFile> allFiles = new ArrayList<>();
            //  é‡‡è´­å®¡æ‰¹æŸ¥è¯¢
            if (record.getApproveType() == 5) {
                String contractNo = record.getApproveReason();
                PurchaseLedger ledger = purchaseLedgerMapper.selectOne(new LambdaQueryWrapper<PurchaseLedger>()
                        .eq(PurchaseLedger::getPurchaseContractNumber, contractNo)
                        .last("limit 1"));
                if (ledger != null) {
                    allFiles = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                            .eq(CommonFile::getCommonId, ledger.getId())
                            .eq(CommonFile::getType, FileNameType.PURCHASE.getValue()));
                }
            }
            //  å‘货审批查询
            else if (record.getApproveType() == 7) {
                String reason = record.getApproveReason(); // æ ¼å¼ä¸º "xx:...-..."
                if (StringUtils.hasText(reason) && reason.contains(":")) {
                    // æå–冒号后面的发货单号
                    String shippingNo = reason.split(":")[1];
                    // æ ¹æ®å‘货单号查询发货台账记录
                    ShippingInfo shippingInfo = shippingInfoMapper.selectOne(new LambdaQueryWrapper<ShippingInfo>()
                            .eq(ShippingInfo::getShippingNo, shippingNo)
                            .last("limit 1"));
                    if (shippingInfo != null) {
                        // ä½¿ç”¨å‘货台账的 é”€å”®å°è´¦ID åŽ»æŸ¥é™„ä»¶
                        allFiles = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                                .eq(CommonFile::getCommonId, shippingInfo.getSalesLedgerId())
                                .eq(CommonFile::getType, FileNameType.SALE.getValue()));
                    }
                }
            }
            //  æŸ¥è¯¢å®¡æ‰¹å•自身的附件
            else {
                allFiles = commonFileMapper.selectList(new LambdaQueryWrapper<CommonFile>()
                    .eq(CommonFile::getCommonId, record.getId())
                    .eq(CommonFile::getType, FileNameType.ApproveProcess.getValue()));
            record.setCommonFileList(commonFiles);
            }
            record.setCommonFileList(allFiles);
        }
        return approveProcessIPage;
    }
src/main/java/com/ruoyi/basic/controller/ProductController.java
@@ -5,10 +5,12 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductModelDto;
import com.ruoyi.basic.dto.ProductModelExportDto;
import com.ruoyi.basic.dto.ProductTreeDto;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.basic.service.IProductService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
@@ -23,6 +25,7 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
@RestController
@@ -124,9 +127,20 @@
    /**
     * å¯¼å…¥äº§å“
     */
    @PostMapping("/import")
    @Log(title = "导入产品",businessType = BusinessType.IMPORT)
    @PostMapping("import")
    public AjaxResult importProduct(MultipartFile file) {
        return AjaxResult.success(productModelService.importProduct(file));
    public AjaxResult importProductModel(@RequestParam("file") MultipartFile file, Integer productId) {
        return productModelService.importProductModel(file, productId);
    }
    /**
     * äº§å“å¯¼å…¥æ¨¡æ¿
     */
    @GetMapping("/export")
    @ApiOperation("产品导入模板")
    @Log(title = "产品导入模板", businessType = BusinessType.EXPORT)
    public void importProduct(HttpServletResponse response) {
        ExcelUtil<ProductModelExportDto> excelUtil = new ExcelUtil<>(ProductModelExportDto.class);
        excelUtil.importTemplateExcel(response, "产品规格导入模板");
    }
}
src/main/java/com/ruoyi/basic/dto/ProductModelExportDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.ruoyi.basic.dto;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import lombok.Data;
/**
 * <br>
 * äº§å“å¯¼å‡ºæ¨¡æ¿
 * </br>
 *
 * @author deslrey
 * @version 1.0
 * @since 2026/02/10 14:39
 */
@Data
public class ProductModelExportDto {
    @Excel(name = "规格型号")
    private String model;
    @Excel(name = "单位")
    private String unit;
}
src/main/java/com/ruoyi/basic/service/IProductModelService.java
@@ -6,6 +6,7 @@
import com.ruoyi.basic.dto.ProductDto;
import com.ruoyi.basic.dto.ProductModelDto;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
@@ -33,5 +34,5 @@
     */
    IPage<ProductModel> modelListPage(Page page , ProductDto productDto);
    Boolean importProduct(MultipartFile file);
    AjaxResult importProductModel(MultipartFile file, Integer productId);
}
src/main/java/com/ruoyi/basic/service/impl/ProductModelServiceImpl.java
@@ -3,6 +3,7 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.basic.dto.ProductDto;
@@ -12,20 +13,19 @@
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.basic.service.IProductModelService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -55,7 +55,6 @@
    }
    @Override
    public int delProductModel(Long[] ids) {
        List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectList(new QueryWrapper<SalesLedgerProduct>()
@@ -76,6 +75,7 @@
    /**
     * æ ¹æ®id查询产品规格分页查询
     *
     * @param page
     * @param productDto
     * @return
@@ -88,24 +88,67 @@
    }
    @Override
    public Boolean importProduct(MultipartFile file) {
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult importProductModel(MultipartFile file, Integer productId) {
        if (productId == null) {
            return AjaxResult.error("请先选择产品再导入规格型号");
        }
        Product product = productMapper.selectById(productId);
        if (product == null) {
            return AjaxResult.error("选择的产品不存在");
        }
        try {
            ExcelUtil<ProductModel> productModelExcelUtil = new ExcelUtil<>(ProductModel.class);
            List<ProductModel> productModelList = productModelExcelUtil.importExcel(file.getInputStream());
            Map<String, List<ProductModel>> collect = productModelList.stream().collect(Collectors.groupingBy(ProductModel::getProductName));
            collect.forEach((k,v)->{
                Product product = productMapper.selectOne(new LambdaQueryWrapper<Product>().eq(Product::getProductName, k).last("LIMIT 1"));
                if (product != null) {
                    v.forEach(productModel -> {
                        productModel.setProductId(product.getId());
                    });
                    this.saveOrUpdateBatch(v);
            if (CollectionUtils.isEmpty(productModelList)) {
                return AjaxResult.error("导入数据不能为空");
                }
            });
            return true;
            //  èŽ·å–å½“å‰äº§å“ä¸‹æ‰€æœ‰çš„è§„æ ¼åž‹å·å
            List<ProductModel> existingModels = list(new LambdaQueryWrapper<ProductModel>().eq(ProductModel::getProductId, productId));
            Set<String> existingModelNames = existingModels.stream().map(ProductModel::getModel).collect(Collectors.toSet());
            List<ProductModel> waitToSaveList = new ArrayList<>();
            int skipCount = 0;
            for (int i = 0; i < productModelList.size(); i++) {
                ProductModel item = productModelList.get(i);
                int rowNum = i + 2;
                if (StringUtils.isEmpty(item.getModel())) {
                    return AjaxResult.error("第 " + rowNum + " è¡Œå¯¼å…¥å¤±è´¥: [规格型号] ä¸èƒ½ä¸ºç©º");
                }
                if (StringUtils.isEmpty(item.getUnit())) {
                    return AjaxResult.error("第 " + rowNum + " è¡Œå¯¼å…¥å¤±è´¥: [单位] ä¸èƒ½ä¸ºç©º");
                }
                //  åŽ»é‡,如果已包含该型号,则跳过
                if (existingModelNames.contains(item.getModel())) {
                    skipCount++;
                    continue;
                }
                item.setProductId(product.getId());
                waitToSaveList.add(item);
                existingModelNames.add(item.getModel());
            }
            if (!waitToSaveList.isEmpty()) {
                saveBatch(waitToSaveList);
            }
            if (skipCount == 0) {
                return AjaxResult.success(String.format("成功导入 %d æ¡æ•°æ®", waitToSaveList.size()));
            } else {
                return AjaxResult.success(String.format("成功导入 %d æ¡ï¼Œè·³è¿‡å·²å­˜åœ¨æ•°æ® %d æ¡", waitToSaveList.size(), skipCount));
            }
        }catch (Exception e) {
            e.printStackTrace();
            log.error("导入产品规格异常", e);
            throw new ServiceException("导入失败");
        }
        return false;
    }
}
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);
    }
@@ -79,8 +82,8 @@
    @Log(title = "导出薪资管理列表", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response ) {
        List<CompensationPerformance> list = compensationPerformanceService.list();
        ExcelUtil<CompensationPerformance> util = new ExcelUtil<CompensationPerformance>(CompensationPerformance.class);
        List<CompensationPerformance> list = compensationPerformanceService.exportList();
        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());
            StaffOnJob staffOnJob = staffOnJobMapper.selectStaffByNickName(item.getStaffName());
            if(staffOnJob!=null){
                item.setStaffId(staffOnJob.getUserId());
                item.setStaffId(staffOnJob.getId());
            }
        });
        boolean b = compensationPerformanceService.saveBatch(list);
src/main/java/com/ruoyi/compensationperformance/mapper/CompensationPerformanceMapper.java
@@ -6,6 +6,8 @@
import com.ruoyi.compensationperformance.pojo.CompensationPerformance;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * @author :yys
 * @date : 2025/8/8 9:54
@@ -15,9 +17,17 @@
    /**
     * åˆ†é¡µæŸ¥è¯¢
     *
     * @param page
     * @param compensationPerformance
     * @return
     * @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);
    /**
     * å¯¼å‡ºäººå‘˜è–ªèµ„
     *
     * @return äººå‘˜è–ªèµ„信息
     */
    List<CompensationPerformance> exportList();
}
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
@@ -5,6 +5,8 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.compensationperformance.pojo.CompensationPerformance;
import java.util.List;
/**
 * @author :yys
 * @date : 2025/8/8 9:55
@@ -14,9 +16,17 @@
    /**
     * åˆ†é¡µæŸ¥è¯¢
     *
     * @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);
    /**
     * å¯¼å‡ºäººå‘˜æ–°å¢ž
     *
     * @return äººå‘˜è–ªèµ„
     */
    List<CompensationPerformance> exportList();
}
src/main/java/com/ruoyi/compensationperformance/service/impl/CompensationPerformanceServiceImpl.java
@@ -10,6 +10,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * @author :yys
 * @date : 2025/8/8 9:55
@@ -23,7 +25,12 @@
    @Override
    public IPage<CompensationPerformance> listPage(Page page, CompensationPerformance compensationPerformance) {
        return compensationPerformanceMapper.listPage(page, compensationPerformance);
    public IPage<CompensationPerformance> listPage(Page page, String staffName, String payDateStr) {
        return compensationPerformanceMapper.listPage(page, staffName, payDateStr);
    }
    @Override
    public List<CompensationPerformance> exportList() {
        return compensationPerformanceMapper.exportList();
    }
}
src/main/java/com/ruoyi/customervisits/service/impl/CustomerVisitsServiceImpl.java
@@ -26,11 +26,19 @@
    @Override
    public IPage<CustomerVisits> listPage(Page page, CustomerVisits customerVisits) {
        LambdaQueryWrapper<CustomerVisits> customerVisitsLambdaQueryWrapper = new LambdaQueryWrapper<>();
        if (customerVisits != null && !StringUtils.isEmpty(customerVisits.getCustomerName())) {
            customerVisitsLambdaQueryWrapper.like(CustomerVisits::getCustomerName, customerVisits.getCustomerName());
        LambdaQueryWrapper<CustomerVisits> wrapper = new LambdaQueryWrapper<>();
        if (customerVisits != null) {
            if (StringUtils.hasText(customerVisits.getCustomerName())) {
                wrapper.like(CustomerVisits::getCustomerName, customerVisits.getCustomerName());
        }
        return customerVisitsMapper.selectPage(page, customerVisitsLambdaQueryWrapper);
            if (StringUtils.hasText(customerVisits.getVisitingPeople())) {
                wrapper.like(CustomerVisits::getVisitingPeople, customerVisits.getVisitingPeople());
            }
        }
        return customerVisitsMapper.selectPage(page, wrapper);
    }
    @Override
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;
@@ -145,6 +146,12 @@
    @Autowired
    private ProductProcessMapper productProcessMapper;
    @Autowired
    private AccountExpenseMapper accountExpenseMapper;
    @Autowired
    private AccountIncomeMapper accountIncomeMapper;
    @Override
    public HomeBusinessDto business() {
        // æž„建结果
@@ -178,28 +185,31 @@
        LambdaQueryWrapper<PurchaseLedger> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.ge(PurchaseLedger::getEntryDate, currentMonth.atDay(1).atStartOfDay()) // å¤§äºŽç­‰äºŽæœ¬æœˆç¬¬ä¸€å¤©
                .lt(PurchaseLedger::getEntryDate, currentMonth.plusMonths(1).atDay(1).atStartOfDay()); // å°äºŽä¸‹æœˆç¬¬ä¸€å¤©
        // æ‰§è¡ŒæŸ¥è¯¢å¹¶è®¡ç®—总和
        List<PurchaseLedger> purchaseLedgers = purchaseLedgerMapper.selectList(queryWrapper);
        if (!CollectionUtils.isEmpty(purchaseLedgers)) {
            LambdaQueryWrapper<SalesLedgerProduct> salesLedgerProductMapperLambdaQueryWrapperCopy = new LambdaQueryWrapper<SalesLedgerProduct>();
            salesLedgerProductMapperLambdaQueryWrapperCopy.eq(SalesLedgerProduct::getType, 2)
            LambdaQueryWrapper<SalesLedgerProduct> salesLedgerProductMapperLambdaQueryWrapperCopy = new LambdaQueryWrapper<>();
            salesLedgerProductMapperLambdaQueryWrapperCopy.eq(SalesLedgerProduct::getType, 2) // é‡‡è´­ç±»åž‹
                    .in(SalesLedgerProduct::getSalesLedgerId,
                            purchaseLedgers.stream().map(PurchaseLedger::getId).collect(Collectors.toList()));
            List<SalesLedgerProduct> salesLedgerProductsCopy = salesLedgerProductMapper
                    .selectList(salesLedgerProductMapperLambdaQueryWrapperCopy);
            // åˆè®¡åˆåŒé‡‘额
            // åˆè®¡åˆåŒæ€»é‡‘额
            BigDecimal receiveAmount = purchaseLedgers.stream()
                    .map(PurchaseLedger::getContractAmount)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            // æœªå¼€ç¥¨é‡‘额
            //  å¾…付款总金额
            BigDecimal unReceiptPaymentAmount = salesLedgerProductsCopy.stream()
                    .map(SalesLedgerProduct::getNoInvoiceAmount)
                    .map(SalesLedgerProduct::getPendingTicketsTotal)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);
            homeBusinessDto.setMonthPurchaseMoney(receiveAmount.setScale(2, RoundingMode.HALF_UP).toString());
            homeBusinessDto
                    .setMonthPurchaseHaveMoney(unReceiptPaymentAmount.setScale(2, RoundingMode.HALF_UP).toString());
            homeBusinessDto.setMonthPurchaseHaveMoney(unReceiptPaymentAmount.setScale(2, RoundingMode.HALF_UP).toString());
        }
        // ç»Ÿè®¡åº“å­˜
        BigDecimal stockQuantityTotal = stockInventoryMapper.selectTotal();
@@ -330,10 +340,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))
@@ -1031,28 +1041,20 @@
    @Override
    public List<MapDto> salesPurchaseStorageProductCount() {
        LocalDate now = LocalDate.now();
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        String currentMonthStart = now.with(TemporalAdjusters.firstDayOfMonth()).format(dtf);
        String currentMonthNow = now.format(dtf);
        LocalDate lastMonth = now.minusMonths(1);
        String lastMonthStart = lastMonth.with(TemporalAdjusters.firstDayOfMonth()).format(dtf);
        String lastMonthEnd = lastMonth.with(TemporalAdjusters.lastDayOfMonth()).format(dtf);
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime currentMonthStart = now.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
        LocalDateTime lastMonth = now.minusMonths(1);
        LocalDateTime lastMonthStart = lastMonth.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);
        LocalDateTime lastMonthEnd = lastMonth.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);
        // é”€å”®
        int currentSales = salesLedgerProductMapper.selectProductCountByTypeAndDate(1, currentMonthStart,
                currentMonthNow);
        int currentSales = salesLedgerProductMapper.selectProductCountByTypeAndDate(1, currentMonthStart, now);
        int lastSales = salesLedgerProductMapper.selectProductCountByTypeAndDate(1, lastMonthStart, lastMonthEnd);
        // é‡‡è´­
        int currentPurchase = salesLedgerProductMapper.selectProductCountByTypeAndDate(2, currentMonthStart,
                currentMonthNow);
        int currentPurchase = salesLedgerProductMapper.selectProductCountByTypeAndDate(2, currentMonthStart, now);
        int lastPurchase = salesLedgerProductMapper.selectProductCountByTypeAndDate(2, lastMonthStart, lastMonthEnd);
        // å‚¨å­˜
        int currentStorage = stockInventoryMapper.selectStorageProductCountByDate(currentMonthStart, currentMonthNow);
        int currentStorage = stockInventoryMapper.selectStorageProductCountByDate(currentMonthStart, now);
        int lastStorage = stockInventoryMapper.selectStorageProductCountByDate(lastMonthStart, lastMonthEnd);
        List<MapDto> list = new ArrayList<>();
@@ -1151,12 +1153,6 @@
    public List<MapDto> productTurnoverDays() {
        return homeMapper.productTurnoverDays();
    }
    @Autowired
    private AccountExpenseMapper accountExpenseMapper;
    @Autowired
    private AccountIncomeMapper accountIncomeMapper;
    public List<Map<String, Object>> incomeExpenseAnalysis(Integer type) {
@@ -1281,36 +1277,21 @@
        String startStr = startDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        String endStr = endDate.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        // List<IncomeExpenseAnalysisDto> incomeList =
        // salesLedgerMapper.selectIncomeStats(startStr, endStr, dateFormat);
        List<IncomeExpenseAnalysisDto> incomeList = new ArrayList<>();
        List<IncomeExpenseAnalysisDto> purchaseList = purchaseLedgerMapper.selectPurchaseStats(startStr, endStr,
                dateFormat);
        List<IncomeExpenseAnalysisDto> expenseList = accountExpenseMapper.selectAccountExpenseStats(startStr, endStr,
                dateFormat);
        List<IncomeExpenseAnalysisDto> incomeList = accountIncomeMapper.selectIncomeStats(startStr, endStr, dateFormat);
        List<IncomeExpenseAnalysisDto> expenseList = accountExpenseMapper.selectAccountExpenseStats(startStr, endStr, dateFormat);
        Map<String, BigDecimal> incomeMap = incomeList.stream().collect(Collectors
                .toMap(IncomeExpenseAnalysisDto::getDateStr, IncomeExpenseAnalysisDto::getAmount, BigDecimal::add));
        Map<String, BigDecimal> purchaseMap = purchaseList.stream().collect(Collectors
                .toMap(IncomeExpenseAnalysisDto::getDateStr, IncomeExpenseAnalysisDto::getAmount, BigDecimal::add));
        Map<String, BigDecimal> expenseMap = expenseList.stream().collect(Collectors
                .toMap(IncomeExpenseAnalysisDto::getDateStr, IncomeExpenseAnalysisDto::getAmount, BigDecimal::add));
        List<MapDto> result = new ArrayList<>();
        for (String month : months) {
            MapDto dto = new MapDto();
            dto.setName(month);
            BigDecimal income = incomeMap.getOrDefault(month, BigDecimal.ZERO);
            BigDecimal purchase = purchaseMap.getOrDefault(month, BigDecimal.ZERO);
            income = BigDecimal.ZERO;
            BigDecimal expense = expenseMap.getOrDefault(month, BigDecimal.ZERO);
            BigDecimal totalExpense = purchase.add(expense);
            BigDecimal profit = income.subtract(totalExpense);
            BigDecimal profit = income.subtract(expense);
            dto.setValue(profit.setScale(2, RoundingMode.HALF_UP).toString());
            result.add(dto);
        }
@@ -1916,10 +1897,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 +1988,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 +2011,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 +2032,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 +2070,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 +2178,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 +2402,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 +2411,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 +2427,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 +2440,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"));
src/main/java/com/ruoyi/inspectiontask/controller/InspectionTaskController.java
@@ -22,7 +22,7 @@
 * @date : 2025/9/19 10:52
 */
@RestController
@Api(tags = "巡检任务管理")
@Api(tags = "巡检任务记录")
@RequestMapping("/inspectionTask")
public class InspectionTaskController extends BaseController {
src/main/java/com/ruoyi/inspectiontask/controller/TimingTaskController.java
@@ -23,7 +23,7 @@
 * @date : 2025/9/19 10:53
 */
@RestController
@Api(tags = "定时任务管理")
@Api(tags = "巡检管理")
@RequestMapping("/timingTask")
public class TimingTaskController extends BaseController {
src/main/java/com/ruoyi/production/controller/ProductOrderController.java
@@ -51,29 +51,6 @@
    @PostMapping("/export")
    public void export(HttpServletResponse response, ProductOrderDto productOrderDto) {
        List<ProductOrderDto> list = productOrderService.pageProductOrder(new Page<>(1, -1), productOrderDto).getRecords();
        if (list != null && !list.isEmpty()) {
            list.forEach(item -> {
                // åˆ¤ç©º
                if (item.getQuantity() == null || item.getCompleteQuantity() == null) {
                    item.setCompletionStatus(BigDecimal.ZERO);
                    return;
                }
                // åˆ¤é›¶
                if (item.getQuantity().compareTo(BigDecimal.ZERO) == 0) {
                    item.setCompletionStatus(BigDecimal.ZERO);
                    return;
                }
                BigDecimal progress = item.getCompleteQuantity()
                        .divide(item.getQuantity(), 4, BigDecimal.ROUND_HALF_UP)
                        .multiply(new BigDecimal(100))
                        .setScale(2, BigDecimal.ROUND_HALF_UP);
                item.setCompletionStatus(progress);
            });
        }
        ExcelUtil<ProductOrderDto> util = new ExcelUtil<>(ProductOrderDto.class);
        util.exportExcel(response, list, "生产订单数据");
    }
src/main/java/com/ruoyi/production/controller/ProductionProductMainController.java
@@ -45,9 +45,7 @@
    @ApiOperation("删除报工")
    @DeleteMapping("/delete")
    @Transactional(rollbackFor = Exception.class)
    public R delete(@RequestBody ProductionProductMainDto productionProductMainDto) {
        return R.ok(productionProductMainService.removeProductMain(productionProductMainDto.getId()));
    }
src/main/java/com/ruoyi/production/dto/ProductionProductMainDto.java
@@ -54,6 +54,8 @@
    private LocalDate schedulingDate;
    private String schedulingUserName;
    private String customerName;
    //工序
    @Excel(name = "工序")
    private String process;
    private BigDecimal workHours;
    private BigDecimal wages;
src/main/java/com/ruoyi/production/service/impl/ProductOrderServiceImpl.java
@@ -4,6 +4,7 @@
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
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.enums.StockOutQualifiedRecordTypeEnum;
@@ -145,92 +146,27 @@
    @Override
    public Boolean delete(Long[] ids) {
        //批量查询productOrder
        List<ProductOrder> productOrders = productOrderMapper.selectList(
                new LambdaQueryWrapper<ProductOrder>()
                        .in(ProductOrder::getId, ids)
        );
        if (!org.springframework.util.CollectionUtils.isEmpty(productOrders)) {
            // æ‰¹é‡æŸ¥è¯¢processRouteItems
            List<ProductProcessRouteItem> allRouteItems = productProcessRouteItemMapper.selectList(
                    new LambdaQueryWrapper<ProductProcessRouteItem>()
                            .in(ProductProcessRouteItem::getProductOrderId, ids)
            );
            if (!com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(allRouteItems)) {
                // èŽ·å–è¦åˆ é™¤çš„å·¥åºé¡¹ID
                List<Long> routeItemIds = allRouteItems.stream()
                        .map(ProductProcessRouteItem::getId)
                        .collect(Collectors.toList());
                // æŸ¥è¯¢å…³è”的工单ID
                List<ProductWorkOrder> workOrders = productWorkOrderMapper.selectList(
                        new LambdaQueryWrapper<ProductWorkOrder>()
                                .in(ProductWorkOrder::getProductProcessRouteItemId, routeItemIds)
                );
                if (!com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(workOrders)) {
                    List<Long> workOrderIds = workOrders.stream()
                            .map(ProductWorkOrder::getId)
                            .collect(Collectors.toList());
                    // æŸ¥è¯¢å…³è”的生产主表ID
                    List<ProductionProductMain> productMains = productionProductMainMapper.selectList(
                            new LambdaQueryWrapper<ProductionProductMain>()
                                    .in(ProductionProductMain::getWorkOrderId, workOrderIds)
                    );
                    List<Long> productMainIds = productMains.stream()
                            .map(ProductionProductMain::getId)
                            .collect(Collectors.toList());
                    // åˆ é™¤äº§å‡ºè¡¨ã€æŠ•入表数据
                    if (!com.baomidou.mybatisplus.core.toolkit.CollectionUtils.isEmpty(productMainIds)) {
                        productionProductOutputMapper.deleteByProductMainIds(productMainIds);
                        productionProductInputMapper.deleteByProductMainIds(productMainIds);
                        List<QualityInspect> qualityInspects = qualityInspectMapper.selectList(
                                new LambdaQueryWrapper<QualityInspect>()
                                        .in(QualityInspect::getProductMainId, productMainIds)
                        );
                        //删除出库记录
                        for (Long productMainId : productMainIds) {
                            //删除生产出库记录
                            stockUtils.deleteStockOutRecord(productMainId, StockOutQualifiedRecordTypeEnum.PRODUCTION_REPORT_STOCK_OUT.getCode());
                            //删除报废的入库记录
                            stockUtils.deleteStockInRecord(productMainId, StockInUnQualifiedRecordTypeEnum.PRODUCTION_SCRAP.getCode());
        //如果已经开始生产,不能删除
        //查询生产订单下的工单
        List<ProductWorkOrder> productWorkOrders = productWorkOrderMapper.selectList(Wrappers.<ProductWorkOrder>lambdaQuery().in(ProductWorkOrder::getProductOrderId, ids));
        if (productWorkOrders.size()>0){
            //判断是否有报工数据
            List<ProductionProductMain> productionProductMains = productionProductMainMapper.selectList(Wrappers.<ProductionProductMain>lambdaQuery()
                    .in(ProductionProductMain::getWorkOrderId, productWorkOrders.stream().map(ProductWorkOrder::getId).collect(Collectors.toList())));
            if (productionProductMains.size()>0){
                throw new RuntimeException("生产订单已经开始生产,不能删除");
                        }
                        qualityInspects.forEach(qualityInspect -> {
                            //inspectState=1 å·²æäº¤ ä¸èƒ½åˆ é™¤
                            if (qualityInspect.getInspectState() == 1) {
                                throw new RuntimeException("已提交的检验单不能删除");
            //删除工单
            productWorkOrderMapper.delete(Wrappers.<ProductWorkOrder>lambdaQuery().in(ProductWorkOrder::getProductOrderId, ids));
                            }
                        });
                        qualityInspectMapper.deleteByProductMainIds(productMainIds);
                        salesLedgerProductionAccountingMapper.delete(new LambdaQueryWrapper<SalesLedgerProductionAccounting>()
                                .in(SalesLedgerProductionAccounting::getProductMainId, productMainIds));
                    }
                    // åˆ é™¤ç”Ÿäº§ä¸»è¡¨æ•°æ®
                    productionProductMainMapper.deleteByWorkOrderIds(workOrderIds);
                    // åˆ é™¤å·¥å•数据
                    productWorkOrderMapper.delete(new LambdaQueryWrapper<ProductWorkOrder>()
                            .in(ProductWorkOrder::getProductProcessRouteItemId, routeItemIds));
                }
            }
            // æ‰¹é‡åˆ é™¤processRouteItem
        //删除工艺路线
            productProcessRouteItemMapper.delete(new LambdaQueryWrapper<ProductProcessRouteItem>()
                    .in(ProductProcessRouteItem::getProductOrderId, ids));
            // æ‰¹é‡åˆ é™¤productProcessRoute
            productProcessRouteMapper.delete(new LambdaQueryWrapper<ProductProcessRoute>()
                    .in(ProductProcessRoute::getProductOrderId, ids));
            // æ‰¹é‡åˆ é™¤productOrder
        //删除生产订单
            productOrderMapper.delete(new LambdaQueryWrapper<ProductOrder>()
                    .in(ProductOrder::getId, ids));
        }
        return true;
    }
src/main/java/com/ruoyi/production/service/impl/ProductionProductMainServiceImpl.java
@@ -40,6 +40,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
@AllArgsConstructor
@@ -61,6 +62,7 @@
    private ProductModelMapper productModelMapper;
    private QualityInspectMapper qualityInspectMapper;
    private QualityUnqualifiedMapper qualityUnqualifiedMapper;
    private ProductProcessMapper productProcessMapper;
    private ProductProcessRouteMapper productProcessRouteMapper;
@@ -254,8 +256,16 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean removeProductMain(Long id) {
        //判断该条报工是否不合格处理,如果不合格处理了,则不允许删除
        List<QualityInspect> qualityInspects = qualityInspectMapper.selectList(Wrappers.<QualityInspect>lambdaQuery().eq(QualityInspect::getProductMainId, id));
        if (qualityInspects.size() > 0){
            List<QualityUnqualified> qualityUnqualifieds = qualityUnqualifiedMapper.selectList(Wrappers.<QualityUnqualified>lambdaQuery()
                    .in(QualityUnqualified::getInspectId, qualityInspects.stream().map(QualityInspect::getId).collect(Collectors.toList())));
            if (qualityUnqualifieds.size() > 0 && qualityUnqualifieds.get(0).getInspectState()==1) {
                throw new ServiceException("该条报工已经不合格处理了,不允许删除");
            }
        }
        ProductionProductMain productionProductMain = productionProductMainMapper.selectById(id);
        //该报工对应的工艺路线详情
        ProductProcessRouteItem productProcessRouteItem = productProcessRouteItemMapper.selectById(productionProductMain.getProductProcessRouteItemId());
@@ -280,7 +290,6 @@
        } else {
            throw new ServiceException("操作失败:工单信息或产出记录不存在");
        }
        //判断是否是最后一道工序
        List<ProductProcessRouteItem> productProcessRouteItems = productProcessRouteItemMapper.selectList(Wrappers.<ProductProcessRouteItem>lambdaQuery().eq(ProductProcessRouteItem::getProductRouteId, productProcessRouteItem.getProductRouteId()));
        if (productProcessRouteItem.getDragSort() != null && productProcessRouteItems != null && productProcessRouteItem.getDragSort() == productProcessRouteItems.size()) {
@@ -289,15 +298,10 @@
                BigDecimal orderCompleteQty = productOrder.getCompleteQuantity() == null ? BigDecimal.ZERO : productOrder.getCompleteQuantity();
                BigDecimal totalQty = productionProductOutput.getQuantity() != null ? productionProductOutput.getQuantity() : BigDecimal.ZERO;
                BigDecimal scrapQty = productionProductOutput.getScrapQty() != null ? productionProductOutput.getScrapQty() : BigDecimal.ZERO;
                BigDecimal actualQualifiedQty = totalQty.subtract(scrapQty);
                BigDecimal newCompleteQty = orderCompleteQty.subtract(actualQualifiedQty);
                productOrder.setCompleteQuantity(newCompleteQty.compareTo(BigDecimal.ZERO) < 0 ? BigDecimal.ZERO : newCompleteQty);
                productOrder.setEndTime(null);
                productOrderMapper.updateById(productOrder);
            } else {
                throw new ServiceException("关联的生产订单不存在");
src/main/java/com/ruoyi/project/system/controller/SysNoticeController.java
@@ -5,17 +5,11 @@
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.R;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.controller.BaseController;
@@ -99,4 +93,11 @@
    {
        return toAjax(noticeService.readAll());
    }
    @PostMapping("appReadNotice")
    @ApiOperation("移动端根据消息ID进行已读")
    public AjaxResult appReadNotice(@RequestParam("noticeId") Long noticeId) {
        boolean result = noticeService.appReadNotice(noticeId);
        return toAjax(result);
    }
}
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/SysNotice.java
@@ -3,8 +3,7 @@
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.apache.commons.lang3.builder.ToStringBuilder;
@@ -20,11 +19,13 @@
 * @author ruoyi
 */
@Data
@TableName("sys_notice")
public class SysNotice
{
    private static final long serialVersionUID = 1L;
    /** å…¬å‘ŠID */
    @TableId(value = "notice_id", type = IdType.AUTO)
    private Long noticeId;
    /** å…¬å‘Šæ ‡é¢˜ */
@@ -48,6 +49,9 @@
    /** è·³è½¬è·¯å¾„ */
    private String jumpPath;
    /** APP跳转路径 */
    private String appJumpPath;
    /** åˆ›å»ºè€… */
    @TableField(fill = FieldFill.INSERT)
    private String createBy;
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/domain/vo/RouterVo.java
@@ -36,6 +36,20 @@
     */
    private String component;
    public String getAppComponent() {
        return appComponent;
    }
    public void setAppComponent(String appComponent) {
        this.appComponent = appComponent;
    }
    /**
     * app组件地址
     */
    private String appComponent;
    /**
     * è·¯ç”±å‚数:如 {"id": 1, "name": "ry"}
     */
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/ISysNoticeService.java
@@ -82,4 +82,11 @@
     */
    void simpleNoticeAll(final String title, final String message,final String jumpPath);
    /**
     * APP点击推送消息更改为已读状态
     *
     * @param noticeId æ¶ˆæ¯ID
     * @return å¤±è´¥/成功
     */
    boolean appReadNotice(Long noticeId);
}
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/SysMenuServiceImpl.java
@@ -172,6 +172,7 @@
            router.setName(getRouteName(menu));
            router.setPath(getRouterPath(menu));
            router.setComponent(getComponent(menu));
            router.setAppComponent(menu.getAppComponent());
            router.setQuery(menu.getQuery());
            router.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
            List<SysMenu> cMenus = menu.getChildren();
@@ -187,6 +188,7 @@
                List<RouterVo> childrenList = new ArrayList<RouterVo>();
                RouterVo children = new RouterVo();
                children.setPath(menu.getPath());
                children.setAppComponent(menu.getAppComponent());
                children.setComponent(menu.getComponent());
                children.setName(getRouteName(menu.getRouteName(), menu.getPath()));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), StringUtils.equals("1", menu.getIsCache()), menu.getPath()));
@@ -202,6 +204,7 @@
                RouterVo children = new RouterVo();
                String routerPath = innerLinkReplaceEach(menu.getPath());
                children.setPath(routerPath);
                children.setAppComponent(menu.getAppComponent());
                children.setComponent(UserConstants.INNER_LINK);
                children.setName(getRouteName(menu.getRouteName(), routerPath));
                children.setMeta(new MetaVo(menu.getMenuName(), menu.getIcon(), menu.getPath()));
src/main/java/com/ruoyi/project/system/service/impl/SysNoticeServiceImpl.java
@@ -28,6 +28,7 @@
import com.ruoyi.project.system.domain.SysNotice;
import com.ruoyi.project.system.mapper.SysNoticeMapper;
import com.ruoyi.project.system.service.ISysNoticeService;
import org.springframework.transaction.annotation.Transactional;
/**
 * å…¬å‘Š æœåŠ¡å±‚å®žçŽ°
@@ -52,6 +53,9 @@
    @Autowired
    @Lazy
    private ISysNoticeService sysNoticeService;
    @Autowired
    private UnipushService unipushService;
    /**
     * æŸ¥è¯¢å…¬å‘Šä¿¡æ¯
@@ -144,8 +148,15 @@
    public void simpleNoticeByUser(String title, String message,  List<Long> consigneeId, String jumpPath) {
        Long userId = SecurityUtils.getLoginUser().getUserId();
        Long tenantId = SecurityUtils.getLoginUser().getTenantId();
        List<SysNotice> sysNotices = consigneeId.stream().map(it -> convertSysNotice(title, message, it,tenantId, jumpPath, userId)).collect(Collectors.toList());
        List<SysNotice> sysNotices = consigneeId.stream()
                .map(it -> convertSysNotice(title, message, it, tenantId, jumpPath, unipushService.convertWebPathToAppPath(jumpPath), userId))
                .collect(Collectors.toList());
        sysNoticeService.saveBatch(sysNotices);
        try {
            unipushService.sendClientMessage(sysNotices);
        } catch (Exception e) {
            log.error("APP推送通知失败,原因: {}", e);
        }
    }
    @Override
@@ -192,15 +203,21 @@
                        it.getUserId(),
                        it.getTenantId(),
                        jumpPath,
                        unipushService.convertWebPathToAppPath(jumpPath),
                        userId
                ))
                .collect(Collectors.toList());
        sysNoticeService.saveBatch(collect);
        try {
            unipushService.sendClientMessage(collect);
        } catch (Exception e) {
            log.error("APP推送通知失败,原因: {}", e);
        }
    }
    private SysNotice convertSysNotice(String title,String message,Long consigneeId, Long tenantId,String jumpPath,Long currentUserId) {
    private SysNotice convertSysNotice(String title,String message,Long consigneeId, Long tenantId,String jumpPath,String appJumpPath,Long currentUserId) {
        SysNotice sysNotice = new SysNotice();
        sysNotice.setNoticeType("1");
        sysNotice.setNoticeTitle(title);//标题
@@ -209,8 +226,25 @@
        sysNotice.setConsigneeId(consigneeId);
        sysNotice.setSenderId(currentUserId);
        sysNotice.setJumpPath(jumpPath);
        sysNotice.setAppJumpPath(appJumpPath);
        sysNotice.setTenantId(tenantId);
        return sysNotice;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean appReadNotice(Long noticeId) {
        if (noticeId == null) {
            return false;
        }
        SysNotice sysNotice = noticeMapper.selectNoticeById(noticeId);
        if (sysNotice == null) {
            return false;
        }
        sysNotice.setStatus("1");
        return noticeMapper.update(null, Wrappers.<SysNotice>lambdaUpdate()
                .eq(SysNotice::getNoticeId, noticeId)
                .eq(SysNotice::getStatus, "0")
                .set(SysNotice::getStatus, "1")) > 0;
    }
}
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,208 @@
package com.ruoyi.project.system.service.impl;
import com.alibaba.fastjson2.JSON;
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.HashMap;
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(
                    sysNotice.getNoticeId(),
                    client.getCid(),
                    sysNotice.getNoticeTitle(),
                    sysNotice.getRemark() != null ? sysNotice.getRemark() : sysNotice.getNoticeContent(),
                    appPath
            );
        }
    }
    /**
     * å°† Web ç«¯åˆ†å±‚全路径转换为 App ç«¯ç»„件路由
     */
    public 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(Long noticeId, String cid, String title, String content, String targetPath) {
        log.info("准备推送消息:NoticeId={}, CID={}, Title={}, TargetPath={}", noticeId, cid, title, targetPath);
        PushDTO<Audience> pushDTO = new PushDTO<>();
        pushDTO.setRequestId("REQ_" + System.currentTimeMillis());
        // åœ¨çº¿é€ä¼ å†…容
        PushMessage pushMessage = new PushMessage();
        Map<String, Object> pushMessageMap = new HashMap<>();
        Map<String, Object> payloadMap = new HashMap<>();
        pushMessageMap.put("title", title);
        pushMessageMap.put("content", content);
        payloadMap.put("url", targetPath);
        payloadMap.put("noticeId", noticeId);
        pushMessageMap.put("payload", JSON.toJSONString(payloadMap));
        String transmissionContent = JSON.toJSONString(pushMessageMap);
        pushMessage.setTransmission(transmissionContent);
        pushDTO.setPushMessage(pushMessage);
        // æŽ¥æ”¶äºº
        Audience audience = new Audience();
        audience.addCid(cid);
        pushDTO.setAudience(audience);
        // ç¦»çº¿æŽ¨é€é€šé“
//        pushDTO.setPushChannel(getPushChannel(noticeId, 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(Long noticeId, 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/PaymentRegistrationServiceImpl.java
@@ -127,7 +127,7 @@
            // 2. å¤„理账户支出
            AccountExpense accountExpense = new AccountExpense();
            accountExpense.setExpenseDate(purchaseLedger.getEntryDate());
            accountExpense.setExpenseType("0");
            accountExpense.setExpenseType("4");
            accountExpense.setSupplierName(purchaseLedger.getSupplierName());
            accountExpense.setExpenseMoney(paymentRegistration.getCurrentPaymentAmount());
            accountExpense.setExpenseDescribed("付款支出");
src/main/java/com/ruoyi/purchase/service/impl/ProductRecordServiceImpl.java
@@ -80,27 +80,45 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult updateRecord(ProductRecordDto productRecordDto) {
        SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(productRecordDto.getSaleLedgerProjectId());
        ProductRecord productRecord = productRecordMapper.selectById(productRecordDto.getId());
        if (productRecord == null) return AjaxResult.error("记录不存在");
        //  æ›´æ–°äº§å“å°è´¦
        SalesLedgerProduct salesLedgerProduct = salesLedgerProductMapper.selectById(productRecord.getSaleLedgerProjectId());
        if (salesLedgerProduct != null) {
            salesLedgerProduct.setFutureTicketsAmount(salesLedgerProduct.getFutureTicketsAmount().add(productRecord.getTicketsAmount()).subtract(productRecordDto.getTicketsAmount()));
            salesLedgerProduct.setFutureTickets(salesLedgerProduct.getFutureTickets().add(productRecord.getTicketsNum().subtract(productRecordDto.getTicketsNum())));
            // æœªæ¥ç¥¨é‡‘额 = åŽŸæœªæ¥ç¥¨é‡‘é¢ + æ—§è¡Œé‡‘额 - æ–°è¡Œé‡‘额
            BigDecimal futureTicketsAmount = salesLedgerProduct.getFutureTicketsAmount()
                    .add(productRecord.getTicketsAmount())
                    .subtract(productRecordDto.getTicketsAmount());
            salesLedgerProduct.setFutureTicketsAmount(futureTicketsAmount);
            // æœªæ¥ç¥¨æ•° = åŽŸæœªæ¥ç¥¨æ•° + æ—§è¡Œæ•°é‡ - æ–°è¡Œæ•°é‡
            BigDecimal futureTickets = salesLedgerProduct.getFutureTickets()
                    .add(productRecord.getTicketsNum())
                    .subtract(productRecordDto.getTicketsNum());
            salesLedgerProduct.setFutureTickets(futureTickets);
            // æ›´æ–°äº§å“è¡¨æœ¬æ¬¡æ•°å€¼
            salesLedgerProduct.setTicketsAmount(productRecordDto.getTicketsAmount());
            salesLedgerProduct.setTicketsNum(productRecordDto.getTicketsNum());
            salesLedgerProductMapper.updateById(salesLedgerProduct);
        }
        PurchaseLedger purchaseLedger = purchaseLedgerMapper.selectById(productRecord.getPurchaseLedgerId());
        if (purchaseLedger != null) {
            purchaseLedger.setReceiptPaymentAmount(purchaseLedger.getReceiptPaymentAmount());
        }
        // ä¿®æ”¹å‘票号
        //  æ›´æ–°æ¥ç¥¨ç™»è®°
        TicketRegistration ticketRegistration = ticketRegistrationMapper.selectById(productRecord.getTicketRegistrationId());
        if(ticketRegistration != null){
            // é‡‘额 = æ–°é‡‘额 - æ—§é‡‘额
            BigDecimal amountDiff = productRecordDto.getTicketsAmount().subtract(productRecord.getTicketsAmount());
            // æ€»é‡‘额 = åŽŸæ€»é‡‘é¢ + å·®å€¼
            ticketRegistration.setInvoiceAmount(ticketRegistration.getInvoiceAmount().add(amountDiff));
            // æ›´æ–°å‘票号
            ticketRegistration.setInvoiceNumber(productRecordDto.getInvoiceNumber());
            ticketRegistration.setInvoiceAmount(productRecordDto.getTicketsAmount());
            ticketRegistrationMapper.updateById(ticketRegistration);
        }
        BeanUtils.copyProperties(productRecordDto,productRecord);
        // é‡æ–°è®¡ç®—未来票金额(根据剩余票数 * å•价)
        productRecord.setFutureTicketsAmount(productRecord.getFutureTickets().multiply(productRecord.getTaxInclusiveUnitPrice()));
        productRecordMapper.updateById(productRecord);
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/QualityInspectController.java
@@ -63,16 +63,19 @@
        if(CollectionUtils.isEmpty(ids)){
            return AjaxResult.error("请选择至少一条数据");
        }
        //如果已经提交就不允许删除
        List<QualityInspect> qualityInspects = qualityInspectService.listByIds(ids);
        for (QualityInspect qualityInspect : qualityInspects) {
            if(qualityInspect.getInspectState()==1){
               throw new RuntimeException("已提交的数据不允许删除");
            }
        }
        //删除检验参数
        qualityInspectParamService.remove(Wrappers.<QualityInspectParam>lambdaQuery()
        .in(QualityInspectParam::getInspectId,ids));
        //删除检验附件
        qualityInspectFileService.remove(Wrappers.<QualityInspectFile>lambdaQuery()
        .in(QualityInspectFile::getInspectId,ids));
        //删除入库记录
        for (Integer id : ids) {
            stockUtils.deleteStockInRecord(Long.valueOf(id), StockInQualifiedRecordTypeEnum.QUALITYINSPECT_STOCK_IN.getCode());
        }
        //删除检验单
        return AjaxResult.success(qualityInspectService.removeBatchByIds(ids));
    }
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,9 @@
    IPage<QualityUnqualified> qualityUnqualifiedListPage(Page page, @Param("qualityUnqualified") QualityUnqualified qualityUnqualified);
    List<QualityUnqualified> qualityUnqualifiedExport(@Param("qualityUnqualified") QualityUnqualified qualityUnqualified);
    QualityUnqualified getUnqualified(@Param("id") Integer id);
    //手动新增不合格的时候,根据产品名称和规格型号查出对应的规格型号id
    Long getModelId(@Param("productName") String productName, @Param("model") String model);
}
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/QualityInspectServiceImpl.java
@@ -82,8 +82,12 @@
    @Override
    public int submit(QualityInspect inspect) {
        QualityInspect qualityInspect = qualityInspectMapper.selectById(inspect.getId());
        //提交前必须判断是否合格
        if (ObjectUtils.isNull(qualityInspect.getCheckResult())) {
            throw new RuntimeException("请先判断是否合格");
        }
        /*判断不合格*/
        if (ObjectUtils.isNotNull(qualityInspect.getCheckResult()) && qualityInspect.getCheckResult().equals("不合格")) {
        if (qualityInspect.getCheckResult().equals("不合格")) {
            QualityUnqualified qualityUnqualified = new QualityUnqualified();
            BeanUtils.copyProperties(qualityInspect, qualityUnqualified);
            qualityUnqualified.setInspectState(0);//待处理
src/main/java/com/ruoyi/quality/service/impl/QualityUnqualifiedServiceImpl.java
@@ -132,14 +132,33 @@
                case "让步放行":
                    //调用提交合格的接口
                    stockUtils.addStock(qualityInspect.getProductModelId(), unqualified.getQuantity(), StockInQualifiedRecordTypeEnum.DEFECTIVE_PASS.getCode(), unqualified.getId());
                    qualityInspect.setCheckResult("合格");
                    qualityInspectService.submit(qualityInspect);
                    break;
                default:
                    break;
            }
        } else {
            //查询对应的规格型号id
            Long modelId = qualityUnqualifiedMapper.getModelId(qualityUnqualified.getProductName(), qualityUnqualified.getModel());
            switch (qualityUnqualified.getDealResult()) {
                case "报废":
                    //调用不合格库存接口 å…¥ä¸åˆæ ¼åº“
                    stockUtils.addUnStock(modelId, unqualified.getQuantity(), StockInUnQualifiedRecordTypeEnum.DEFECTIVE_SCRAP.getCode(), unqualified.getId());
                    break;
                case "让步放行":
                    //调用提交合格的接口
                    stockUtils.addStock(modelId, 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/sales/controller/SalesLedgerController.java
@@ -14,10 +14,7 @@
import com.ruoyi.sales.dto.InvoiceLedgerDto;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.mapper.InvoiceLedgerMapper;
import com.ruoyi.sales.mapper.InvoiceRegistrationProductMapper;
import com.ruoyi.sales.mapper.ReceiptPaymentMapper;
import com.ruoyi.sales.pojo.InvoiceLedger;
import com.ruoyi.sales.pojo.InvoiceRegistrationProduct;
import com.ruoyi.sales.pojo.ReceiptPayment;
import com.ruoyi.sales.pojo.SalesLedger;
import com.ruoyi.sales.service.ICommonFileService;
@@ -39,10 +36,7 @@
import java.io.OutputStream;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -64,9 +58,6 @@
    @Autowired
    private InvoiceLedgerMapper invoiceLedgerMapper;
    @Autowired
    private InvoiceRegistrationProductMapper invoiceRegistrationProductMapper;
    @Autowired
    private ReceiptPaymentMapper receiptPaymentMapper;
@@ -268,68 +259,84 @@
    @GetMapping("/listPage")
    public IPage<SalesLedger> listPage(Page page, SalesLedgerDto salesLedgerDto) {
        IPage<SalesLedger> iPage = salesLedgerService.selectSalesLedgerListPage(page,salesLedgerDto);
        // è®¡ç®—已开票金额/未开票金额(已填写发票金额为准)
        //  æŸ¥è¯¢ç»“果为空,直接返回
        if(CollectionUtils.isEmpty(iPage.getRecords())){
            return iPage;
        }
        //  èŽ·å–å½“å‰é¡µæ‰€æœ‰å°è´¦è®°å½•çš„ ID é›†åˆ
        List<Long> salesLedgerIds = iPage.getRecords().stream().map(SalesLedger::getId).collect(Collectors.toList());
        //  æŸ¥è¯¢å‘票信息的已开票金额
        List<InvoiceLedgerDto> invoiceLedgerDtoList = invoiceLedgerMapper.invoicedTotal(salesLedgerIds);
        if(CollectionUtils.isEmpty(invoiceLedgerDtoList)){
            return iPage;
            invoiceLedgerDtoList = Collections.emptyList();
        }
        // è®¡ç®—回款金额,待回款金额
        List<InvoiceRegistrationProduct> invoiceRegistrationProducts = invoiceRegistrationProductMapper.selectList(new LambdaQueryWrapper<InvoiceRegistrationProduct>()
                .in(InvoiceRegistrationProduct::getSalesLedgerId, salesLedgerIds));
        List<InvoiceLedger> invoiceLedgers = invoiceLedgerMapper.selectList(new LambdaQueryWrapper<InvoiceLedger>()
                .in(InvoiceLedger::getInvoiceRegistrationProductId, invoiceRegistrationProducts.stream().map(InvoiceRegistrationProduct::getId).collect(Collectors.toList())));
        List<ReceiptPayment> receiptPayments = new ArrayList<>();
        if(!CollectionUtils.isEmpty(invoiceLedgers)){
        //  è½¬æ¢å‘票数据, key ä¸ºå°è´¦ID, value ä¸ºè¯¥å°è´¦çš„æ€»å¼€ç¥¨é‡‘额
        Map<Long, BigDecimal> invoiceTotals = invoiceLedgerDtoList.stream()
                .filter(dto -> dto.getSalesLedgerId() != null && dto.getInvoiceTotal() != null)
                .collect(Collectors.toMap(
                        dto -> dto.getSalesLedgerId().longValue(),
                        InvoiceLedgerDto::getInvoiceTotal,
                        BigDecimal::add // å­˜åœ¨é‡å¤ID执行累加
                ));
        //  æŸ¥è¯¢å›žæ¬¾/付款记录
        List<ReceiptPayment> receiptPayments = Collections.emptyList();
        if (!CollectionUtils.isEmpty(salesLedgerIds)) {
            receiptPayments = receiptPaymentMapper.selectList(new LambdaQueryWrapper<ReceiptPayment>()
                    .in(ReceiptPayment::getInvoiceLedgerId, invoiceLedgers.stream().map(InvoiceLedger::getId).collect(Collectors.toList())));
                    .in(ReceiptPayment::getSalesLedgerId, salesLedgerIds));
        }
        for (SalesLedger salesLedger : iPage.getRecords()) {
            boolean existFlag = false;
            BigDecimal noInvoiceAmountTotal = BigDecimal.ZERO;
            BigDecimal invoiceTotal = BigDecimal.ZERO;
            for (InvoiceLedgerDto invoiceLedgerDto : invoiceLedgerDtoList) {
                if (salesLedger.getId().intValue() == invoiceLedgerDto.getSalesLedgerId()) {
                    noInvoiceAmountTotal = salesLedger.getContractAmount().subtract(invoiceLedgerDto.getInvoiceTotal());
                    invoiceTotal = invoiceLedgerDto.getInvoiceTotal();
                    existFlag = true;
        //  è½¬æ¢å›žæ¬¾æ•°æ®, key ä¸ºå°è´¦ID, value ä¸ºè¯¥å°è´¦çš„æ€»å›žæ¬¾é‡‘额
        Map<Long, BigDecimal> receiptTotals = new HashMap<>();
                    if(!CollectionUtils.isEmpty(receiptPayments)){
                        List<InvoiceRegistrationProduct> collect = invoiceRegistrationProducts.stream()
                                .filter(item -> salesLedger.getId().equals(Long.parseLong(item.getSalesLedgerId().toString())))
                                .collect(Collectors.toList());
                        List<Integer> collect1 = collect.stream()
                                .map(InvoiceRegistrationProduct::getId).collect(Collectors.toList());
                        List<InvoiceLedger> collect2 = invoiceLedgers.stream()
                                .filter(item -> collect1.contains(item.getInvoiceRegistrationProductId()))
                                .collect(Collectors.toList());
                        // èŽ·å–å·²å›žæ¬¾é‡‘é¢
                        List<ReceiptPayment> collect3 = receiptPayments.stream()
                                .filter(item -> collect2.stream().anyMatch(item1 -> item1.getId().equals(item.getInvoiceLedgerId())))
                                .collect(Collectors.toList());
                        BigDecimal receiptPaymentAmountTotal = collect3.stream().map(ReceiptPayment::getReceiptPaymentAmount)
                                .filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
                        // èŽ·å–å¾…å›žæ¬¾é‡‘é¢
                        BigDecimal noReceiptPaymentAmountTotal = invoiceLedgerDto.getInvoiceTotal().subtract(receiptPaymentAmountTotal);
            for (ReceiptPayment receiptPayment : receiptPayments) {
                if (receiptPayment.getSalesLedgerId() != null && receiptPayment.getReceiptPaymentAmount() != null) {
                    //  å¦‚æžœ key å­˜åœ¨åˆ™ç›¸åŠ ,不存在则放入
                    receiptTotals.merge(receiptPayment.getSalesLedgerId(), receiptPayment.getReceiptPaymentAmount(), BigDecimal::add);
                }
            }
        }
        for (SalesLedger salesLedger : iPage.getRecords()) {
            Long ledgerId = salesLedger.getId();
            // åˆåŒæ€»é‡‘额
            BigDecimal contractAmount = salesLedger.getContractAmount() == null ? BigDecimal.ZERO : salesLedger.getContractAmount();
            // å¼€ç¥¨æ€»é¢å’Œå›žæ¬¾æ€»é¢
            BigDecimal invoiceTotal = invoiceTotals.getOrDefault(ledgerId, BigDecimal.ZERO);
            BigDecimal receiptPaymentAmountTotal = receiptTotals.getOrDefault(ledgerId, BigDecimal.ZERO);
            //  æœªå¼€ç¥¨é‡‘额 = åˆåŒé‡‘额 - å·²å¼€ç¥¨é‡‘额
            BigDecimal noInvoiceAmountTotal = contractAmount.subtract(invoiceTotal);
            if (noInvoiceAmountTotal.compareTo(BigDecimal.ZERO) < 0) {
                noInvoiceAmountTotal = BigDecimal.ZERO;
            }
            //  å¾…回款金额 = å·²å¼€ç¥¨é‡‘额 - å·²å›žæ¬¾é‡‘额
            BigDecimal noReceiptPaymentAmountTotal = invoiceTotal.subtract(receiptPaymentAmountTotal);
            if (noReceiptPaymentAmountTotal.compareTo(BigDecimal.ZERO) < 0) {
                noReceiptPaymentAmountTotal = BigDecimal.ZERO;
            }
            salesLedger.setNoInvoiceAmountTotal(noInvoiceAmountTotal);
            salesLedger.setInvoiceTotal(invoiceTotal);
                        salesLedger.setReceiptPaymentAmountTotal(receiptPaymentAmountTotal);
                        salesLedger.setNoReceiptAmount(noReceiptPaymentAmountTotal);
            //  å¦‚果已经有过开票或回款操作,则不允许编辑
            boolean hasInvoiceOperation = invoiceTotal.compareTo(BigDecimal.ZERO) > 0;
            boolean hasReceiptOperation = receiptPaymentAmountTotal.compareTo(BigDecimal.ZERO) > 0;
            salesLedger.setIsEdit(!(hasInvoiceOperation || hasReceiptOperation));
                    }
                    break;
                }
            }
            if(existFlag){
                salesLedger.setNoInvoiceAmountTotal(noInvoiceAmountTotal);
            }else {
                salesLedger.setNoInvoiceAmountTotal(salesLedger.getContractAmount());
            }
            salesLedger.setInvoiceTotal(invoiceTotal);
        }
        if (ObjectUtils.isNotEmpty(salesLedgerDto.getStatus())) {
            if (salesLedgerDto.getStatus()) {
                iPage.getRecords().removeIf(salesLedger -> Objects.equals(salesLedger.getNoInvoiceAmountTotal(), new BigDecimal("0.00")));
                // æ¸…除所有“未开票金额”为 0 çš„记录
                iPage.getRecords().removeIf(salesLedger ->
                        Objects.equals(salesLedger.getNoInvoiceAmountTotal(), new BigDecimal("0.00")));
                iPage.setTotal(iPage.getRecords().size());
            }
        }
src/main/java/com/ruoyi/sales/controller/SalesLedgerProductController.java
@@ -72,15 +72,14 @@
     * æŸ¥è¯¢äº§å“ä¿¡æ¯åˆ—表
     */
    @GetMapping("/list")
    public AjaxResult list(SalesLedgerProduct salesLedgerProduct)
    {
    public AjaxResult list(SalesLedgerProduct salesLedgerProduct) {
        List<SalesLedgerProduct> list = salesLedgerProductService.selectSalesLedgerProductList(salesLedgerProduct);
        list.forEach(item -> {
                if (item.getFutureTickets().compareTo(BigDecimal.ZERO) == 0) {
                    item.setFutureTickets(item.getQuantity());
                item.setFutureTickets(BigDecimal.ZERO);
                }
            if (item.getFutureTicketsAmount().compareTo(BigDecimal.ZERO) == 0) {
                item.setFutureTicketsAmount(item.getTaxInclusiveTotalPrice());
                item.setFutureTicketsAmount(BigDecimal.ZERO);
            }
//            ProcurementPageDto procurementDto = new ProcurementPageDto();
//            procurementDto.setSalesLedgerProductId(item.getId());
src/main/java/com/ruoyi/sales/dto/StatisticsTableDto.java
@@ -21,11 +21,11 @@
    private String productCategory;
    @ApiModelProperty(value = "开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM")
    private Date entryDateStart;
    @ApiModelProperty(value = "结束时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
    @JsonFormat(pattern = "yyyy-MM")
    private Date entryDateEnd;
}
src/main/java/com/ruoyi/sales/mapper/SalesLedgerMapper.java
@@ -69,7 +69,8 @@
            "SUM(CASE WHEN slp.approve_status = 2 THEN 1 ELSE 0 END) / COUNT(DISTINCT sl.id) * 100), 2) AS ship_rate " +
            "FROM sales_ledger sl " +
            "LEFT JOIN sales_ledger_product slp ON sl.id = slp.sales_ledger_id " +
            "WHERE sl.entry_date BETWEEN #{statisticsTableDto.entryDateStart} AND #{statisticsTableDto.entryDateEnd} " +
            "WHERE sl.entry_date &gt;= DATE_FORMAT(#{statisticsTableDto.entryDateStart}, '%Y-%m-01') " +
            "AND sl.entry_date &lt;= LAST_DAY(#{statisticsTableDto.entryDateEnd}) " +
            // äº§å“å¤§ç±»ç­›é€‰
            "<if test='statisticsTableDto.productCategory != null and statisticsTableDto.productCategory != \"\"'>" +
            "AND slp.product_category = #{statisticsTableDto.productCategory} " +
src/main/java/com/ruoyi/sales/mapper/SalesLedgerProductMapper.java
@@ -11,6 +11,7 @@
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@@ -39,7 +40,7 @@
    List<Map<String, Object>> selectRawMaterialPurchaseAnalysis();
    int selectProductCountByTypeAndDate(@Param("type") Integer type, @Param("startDate") String startDate, @Param("endDate") String endDate);
    int selectProductCountByTypeAndDate(@Param("type") Integer type, @Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate);
    BigDecimal selectRawMaterialExpense();
}
src/main/java/com/ruoyi/sales/pojo/SalesLedger.java
@@ -143,5 +143,9 @@
    @TableField(exist = false)
    //是否发货(台账页面颜色控制)
    private Boolean isFh;
    @TableField(exist = false)
    //是否可编辑
    private Boolean isEdit;
}
src/main/java/com/ruoyi/sales/service/impl/InvoiceRegistrationServiceImpl.java
@@ -3,7 +3,6 @@
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -13,7 +12,10 @@
import com.ruoyi.sales.dto.InvoiceRegistrationProductDto;
import com.ruoyi.sales.dto.SalesLedgerDto;
import com.ruoyi.sales.excel.InvoiceRegisAndProductExcelDto;
import com.ruoyi.sales.mapper.*;
import com.ruoyi.sales.mapper.InvoiceLedgerMapper;
import com.ruoyi.sales.mapper.InvoiceRegistrationMapper;
import com.ruoyi.sales.mapper.InvoiceRegistrationProductMapper;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.InvoiceLedger;
import com.ruoyi.sales.pojo.InvoiceRegistration;
import com.ruoyi.sales.pojo.InvoiceRegistrationProduct;
@@ -85,18 +87,16 @@
                throw new RuntimeException("销售台账产品不存在,ID:" + productDatum.getId());
            }
            //  è®¡ç®—累计开票
            BigDecimal newInvoiceNum = dbProduct.getInvoiceNum().add(currentInvoiceNum);
            // æœªå¼€ç¥¨
            BigDecimal noInvoiceAmount = dbProduct.getNoInvoiceAmount();
            BigDecimal noInvoiceNum = dbProduct.getNoInvoiceNum();
            BigDecimal newInvoiceAmount = dbProduct.getInvoiceAmount().add(currentInvoiceAmount);
            // å‰©ä½™æœªå¼€ç¥¨
            BigDecimal newNoInvoiceNum = noInvoiceNum.subtract(currentInvoiceNum);
            BigDecimal newNoInvoiceAmount = noInvoiceAmount.subtract(currentInvoiceAmount);
            //  è®¡ç®—未开票
            BigDecimal newNoInvoiceNum = dbProduct.getQuantity().subtract(newInvoiceNum);
            BigDecimal newNoInvoiceAmount = dbProduct.getTaxInclusiveTotalPrice().subtract(newInvoiceAmount);
            if (newNoInvoiceNum.compareTo(BigDecimal.ZERO) < 0
                    || newNoInvoiceAmount.compareTo(BigDecimal.ZERO) < 0) {
            if (newNoInvoiceAmount.compareTo(BigDecimal.ZERO) < 0) {
                throw new RuntimeException("开票数量或金额超过合同总量");
            }
@@ -105,8 +105,6 @@
                    null,
                    new LambdaUpdateWrapper<SalesLedgerProduct>()
                            .eq(SalesLedgerProduct::getId, dbProduct.getId())
                            .set(SalesLedgerProduct::getInvoiceNum, newInvoiceNum)
                            .set(SalesLedgerProduct::getInvoiceAmount, newInvoiceAmount)
                            .set(SalesLedgerProduct::getNoInvoiceNum, newNoInvoiceNum)
                            .set(SalesLedgerProduct::getNoInvoiceAmount, newNoInvoiceAmount)
            );
src/main/java/com/ruoyi/sales/service/impl/MetricStatisticsServiceImpl.java
@@ -59,20 +59,74 @@
    public AjaxResult statisticsTable(StatisticsTableDto statisticsTableDto) {
        Map<String, Object> map = new HashMap<>();
        if (statisticsTableDto.getEntryDateStart() == null || statisticsTableDto.getEntryDateEnd() == null) {
            Calendar calendar = Calendar.getInstance();
            // ç»“束时间默认是当前时间
            statisticsTableDto.setEntryDateEnd(new Date());
            // å¼€å§‹æ—¶é—´é»˜è®¤æ˜¯6个月前
            calendar.add(Calendar.MONTH, -6);
            statisticsTableDto.setEntryDateStart(calendar.getTime());
        Date endDate = statisticsTableDto.getEntryDateEnd() != null ? statisticsTableDto.getEntryDateEnd() : new Date();
        statisticsTableDto.setEntryDateEnd(endDate);
        // å¼€å§‹æ—¶é—´é»˜è®¤æ˜¯12个月前
        Date startDate;
        if (statisticsTableDto.getEntryDateStart() != null) {
            startDate = statisticsTableDto.getEntryDateStart();
        } else {
            calendar.setTime(endDate);
            calendar.add(Calendar.MONTH, -11); // å‡11个月,加上当前月共12个月
            calendar.set(Calendar.DAY_OF_MONTH, 1); // è®¾ä¸ºæœˆåˆ
            startDate = calendar.getTime();
        }
        statisticsTableDto.setEntryDateStart(startDate);
        // æŸ¥è¯¢æ•°æ®åº“获取有数据的月份
        List<SalesTrendDto> salesTrendDtos = salesLedgerMapper.statisticsTable(statisticsTableDto);
        if(CollectionUtils.isEmpty(salesTrendDtos)) return AjaxResult.success(map);
        map.put("dateList", salesTrendDtos.stream().map(SalesTrendDto::getMonth).collect(Collectors.toList()));
        map.put("orderCountList", salesTrendDtos.stream().map(SalesTrendDto::getOrderCount).collect(Collectors.toList()));
        map.put("salesAmountList", salesTrendDtos.stream().map(SalesTrendDto::getSalesAmount).collect(Collectors.toList()));
        map.put("shippingRateList", salesTrendDtos.stream().map(SalesTrendDto::getShipRate).collect(Collectors.toList()));
        // åˆ›å»ºæœˆä»½åˆ°æ•°æ®çš„æ˜ å°„
        Map<String, SalesTrendDto> trendMap = new HashMap<>();
        if (!CollectionUtils.isEmpty(salesTrendDtos)) {
            for (SalesTrendDto dto : salesTrendDtos) {
                trendMap.put(dto.getMonth(), dto);
            }
        }
        // ç”Ÿæˆæœˆä»½åˆ—表
        List<String> dateList = new ArrayList<>();
        List<BigDecimal> orderCountList = new ArrayList<>();
        List<BigDecimal> salesAmountList = new ArrayList<>();
        List<BigDecimal> shippingRateList = new ArrayList<>();
        Calendar tempCalendar = Calendar.getInstance();
        tempCalendar.setTime(startDate);
        tempCalendar.set(Calendar.DAY_OF_MONTH, 1); // ç¡®ä¿ä»Žæœˆåˆå¼€å§‹
        Calendar endCalendar = Calendar.getInstance();
        endCalendar.setTime(endDate);
        endCalendar.set(Calendar.DAY_OF_MONTH, 1); // ç¡®ä¿åˆ°æœˆæœ«
        // å¾ªçŽ¯ç”Ÿæˆæœˆä»½åˆ—è¡¨ï¼Œç›´åˆ°è¾¾åˆ°ç»“æŸæœˆä»½
        while (!tempCalendar.after(endCalendar)) {
            String monthStr = String.format("%04d-%02d", tempCalendar.get(Calendar.YEAR), tempCalendar.get(Calendar.MONTH) + 1);
            dateList.add(monthStr);
            // èŽ·å–å½“å‰æœˆä»½çš„æ•°æ®ï¼Œå¦‚æžœæ²¡æœ‰åˆ™ä½¿ç”¨é»˜è®¤å€¼
            SalesTrendDto dto = trendMap.get(monthStr);
            if (dto != null) {
                orderCountList.add(new BigDecimal(dto.getOrderCount()));
                salesAmountList.add(dto.getSalesAmount() != null ? dto.getSalesAmount() : BigDecimal.ZERO);
                shippingRateList.add(new BigDecimal(String.valueOf(dto.getShipRate())));
            } else {
                orderCountList.add(BigDecimal.ZERO);
                salesAmountList.add(BigDecimal.ZERO);
                shippingRateList.add(BigDecimal.ZERO);
            }
            // ä¸‹ä¸€ä¸ªæœˆ
            tempCalendar.add(Calendar.MONTH, 1);
        }
        map.put("dateList", dateList);
        map.put("orderCountList", orderCountList);
        map.put("salesAmountList", salesAmountList);
        map.put("shippingRateList", shippingRateList);
        return AjaxResult.success(map);
    }
}
src/main/java/com/ruoyi/sales/service/impl/ReceiptPaymentServiceImpl.java
@@ -79,9 +79,9 @@
                // å¢žåŠ è´¢åŠ¡æ”¶å…¥è®°å½•
                AccountIncome accountIncome = new AccountIncome();
                accountIncome.setIncomeDate(salesLedger.getEntryDate());
                accountIncome.setIncomeType("0");
                accountIncome.setIncomeType("3");
                accountIncome.setCustomerName(salesLedger.getCustomerName());
                accountIncome.setIncomeMoney(salesLedger.getContractAmount());
                accountIncome.setIncomeMoney(receiptPayment.getReceiptPaymentAmount());
                accountIncome.setIncomeMethod("0");
                accountIncome.setInputTime(new Date());
                accountIncome.setInputUser(salesLedger.getEntryPerson());
src/main/java/com/ruoyi/sales/service/impl/ShippingInfoServiceImpl.java
@@ -72,6 +72,7 @@
        byId.setExpressCompany(req.getExpressCompany());
        byId.setStatus("已发货");
        byId.setShippingCarNumber(req.getShippingCarNumber());
        byId.setShippingDate(req.getShippingDate());
        boolean update = this.updateById(byId);
        // è¿ç§»æ–‡ä»¶
        if(CollectionUtils.isNotEmpty(req.getTempFileIds())){
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,53 @@
package com.ruoyi.staff.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.staff.dto.PersonalAttendanceRecordsDto;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
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.util.CollectionUtils;
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,53 @@
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.PersonalAttendanceRecords;
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/dto/PersonalAttendanceRecordsDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,43 @@
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 io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
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/mapper/PersonalAttendanceLocationConfigMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.staff.mapper;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
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,32 @@
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;
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/StaffOnJobMapper.java
@@ -20,6 +20,7 @@
    /**
     * ç»Ÿè®¡æŒ‡å®šæ—¥æœŸçš„在职员工数
     *
     * @param date æ—¥æœŸ
     * @return åœ¨èŒå‘˜å·¥æ•°
     */
@@ -27,9 +28,18 @@
    /**
     * ç»Ÿè®¡æŒ‡å®šæœˆä»½çš„æ–°å…¥èŒå‘˜å·¥æ•°
     *
     * @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/PersonalAttendanceLocationConfig.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,60 @@
package com.ruoyi.staff.pojo;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.LocalTime;
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;
/**
 * <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
@@ -1,52 +1,84 @@
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")
    @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/StaffOnJob.java
@@ -66,7 +66,7 @@
     /**
     * éƒ¨é—¨
     */
    private Integer sysDeptId;
    private Long sysDeptId;
    /**
     * å®¶åº­ä½å€
src/main/java/com/ruoyi/staff/service/PersonalAttendanceLocationConfigService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package com.ruoyi.staff.service;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.baomidou.mybatisplus.extension.service.IService;
/**
 * <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
@@ -2,9 +2,28 @@
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;
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/PersonalAttendanceLocationConfigServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.ruoyi.staff.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.exception.base.BaseException;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
import com.ruoyi.staff.mapper.PersonalAttendanceLocationConfigMapper;
import com.ruoyi.staff.service.PersonalAttendanceLocationConfigService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <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,260 @@
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.staff.mapper.PersonalAttendanceRecordsMapper;
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.dto.StaffOnJobDto;
import com.ruoyi.staff.mapper.PersonalAttendanceLocationConfigMapper;
import com.ruoyi.staff.mapper.StaffOnJobMapper;
import com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig;
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 com.ruoyi.staff.task.PersonalAttendanceRecordsTask;
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.time.format.DateTimeFormatter;
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/StaffOnJobServiceImpl.java
@@ -180,7 +180,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,76 @@
package com.ruoyi.staff.task;
import com.ruoyi.staff.pojo.PersonalAttendanceRecords;
import com.ruoyi.staff.pojo.StaffOnJob;
import com.ruoyi.staff.service.PersonalAttendanceRecordsService;
import com.ruoyi.staff.mapper.PersonalAttendanceRecordsMapper;
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.math.BigDecimal;
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/stock/mapper/StockInventoryMapper.java
@@ -12,6 +12,7 @@
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@@ -41,7 +42,7 @@
    BigDecimal selectTotal();
    int selectStorageProductCountByDate(@Param("startDate") String startDate, @Param("endDate") String endDate);
    int selectStorageProductCountByDate(@Param("startDate") LocalDateTime startDate, @Param("endDate") LocalDateTime endDate);
    List<Map<String, Object>> selectDailyStockInCounts(@Param("rootCategoryId") Long rootCategoryId, @Param("startDate") String startDate, @Param("endDate") String endDate);
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-khl.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,245 @@
# é¡¹ç›®ç›¸å…³é…ç½®
ruoyi:
  # åç§°
  name: RuoYi
  # ç‰ˆæœ¬
  version: 3.8.9
  # ç‰ˆæƒå¹´ä»½
  copyrightYear: 2025
  # æ–‡ä»¶è·¯å¾„ ç¤ºä¾‹ï¼ˆ Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
  profile: /javaWork/product-inventory-management/file
  # èŽ·å–ip地址开关
  addressEnabled: false
  # éªŒè¯ç ç±»åž‹ math æ•°å­—计算 char å­—符验证
  captchaType: math
  # ååŒå®¡æ‰¹ç¼–号前缀(配置文件后缀命名)
  approvalNumberPrefix: KHL
# å¼€å‘环境配置
server:
  # æœåŠ¡å™¨çš„HTTP端口,默认为8080
  port: 9214
  servlet:
    # åº”用的访问路径
    context-path: /
  tomcat:
    # tomcat的URI编码
    uri-encoding: UTF-8
    # è¿žæŽ¥æ•°æ»¡åŽçš„æŽ’队数,默认为100
    accept-count: 1000
    threads:
      # tomcat最大线程数,默认为200
      max: 800
      # Tomcat启动初始化的线程数,默认值10
      min-spare: 100
# æ—¥å¿—配置
logging:
  level:
    com.ruoyi: warn
    org.springframework: warn
minio:
  endpoint: http://114.132.189.42/
  port: 7019
  secure: false
  accessKey: admin
  secretKey: 12345678
  preview-expiry: 24 # é¢„览地址默认24小时
  default-bucket: jxc
# ç”¨æˆ·é…ç½®
user:
  password:
    # å¯†ç æœ€å¤§é”™è¯¯æ¬¡æ•°
    maxRetryCount: 5
    # å¯†ç é”å®šæ—¶é—´ï¼ˆé»˜è®¤10分钟)
    lockTime: 10
# Spring配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # ä¸»åº“数据源
      master:
        url: jdbc:mysql://172.17.0.1:3306/product-inventory-management-khlnew?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: xd@123456..
      # ä»Žåº“数据源
      slave:
        # ä»Žæ•°æ®æºå¼€å…³/默认关闭
        enabled: false
        url:
        username:
        password:
      # åˆå§‹è¿žæŽ¥æ•°
      initialSize: 5
      # æœ€å°è¿žæŽ¥æ± æ•°é‡
      minIdle: 10
      # æœ€å¤§è¿žæŽ¥æ± æ•°é‡
      maxActive: 20
      # é…ç½®èŽ·å–è¿žæŽ¥ç­‰å¾…è¶…æ—¶çš„æ—¶é—´
      maxWait: 60000
      # é…ç½®è¿žæŽ¥è¶…æ—¶æ—¶é—´
      connectTimeout: 30000
      # é…ç½®ç½‘络超时时间
      socketTimeout: 60000
      # é…ç½®é—´éš”多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # é…ç½®ä¸€ä¸ªè¿žæŽ¥åœ¨æ± ä¸­æœ€å°ç”Ÿå­˜çš„æ—¶é—´ï¼Œå•位是毫秒
      minEvictableIdleTimeMillis: 300000
      # é…ç½®ä¸€ä¸ªè¿žæŽ¥åœ¨æ± ä¸­æœ€å¤§ç”Ÿå­˜çš„æ—¶é—´ï¼Œå•位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # é…ç½®æ£€æµ‹è¿žæŽ¥æ˜¯å¦æœ‰æ•ˆ
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        # è®¾ç½®ç™½åå•,不填则允许所有访问
        allow:
        url-pattern: /druid/*
        # æŽ§åˆ¶å°ç®¡ç†ç”¨æˆ·åå’Œå¯†ç 
        login-username: ruoyi
        login-password: 123456
      filter:
        stat:
          enabled: true
          # æ…¢SQL记录
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true
  # èµ„源信息
  messages:
    # å›½é™…化资源文件路径
    basename: i18n/messages
  # æ–‡ä»¶ä¸Šä¼ 
  servlet:
    multipart:
      # å•个文件大小
      max-file-size: 1GB
      # è®¾ç½®æ€»ä¸Šä¼ çš„æ–‡ä»¶å¤§å°
      max-request-size: 2GB
  # æœåŠ¡æ¨¡å—
  devtools:
    restart:
      # çƒ­éƒ¨ç½²å¼€å…³
      enabled: false
  # redis é…ç½®
  redis:
    # åœ°å€
    #    host: 127.0.0.1
    host: 172.17.0.1
    # ç«¯å£ï¼Œé»˜è®¤ä¸º6379
    port: 6380
    # æ•°æ®åº“索引
    database: 0
    # å¯†ç 
    #    password: root2022!
    password:
    # è¿žæŽ¥è¶…æ—¶æ—¶é—´
    timeout: 10s
    lettuce:
      pool:
        # è¿žæŽ¥æ± ä¸­çš„æœ€å°ç©ºé—²è¿žæŽ¥
        min-idle: 0
        # è¿žæŽ¥æ± ä¸­çš„æœ€å¤§ç©ºé—²è¿žæŽ¥
        max-idle: 8
        # è¿žæŽ¥æ± çš„æœ€å¤§æ•°æ®åº“连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
  # Quartz定时任务配置(新增部分)
  quartz:
    job-store-type: jdbc  # ä½¿ç”¨æ•°æ®åº“存储
    jdbc:
      initialize-schema: never  # é¦–次运行时自动创建表结构,成功后改为never
      schema: classpath:org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql  # MySQL表结构脚本
    properties:
      org:
        quartz:
          scheduler:
            instanceName: RuoYiScheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate  # MySQL适配
            tablePrefix: qrtz_  # è¡¨åå‰ç¼€ï¼Œä¸Žè„šæœ¬ä¸€è‡´
            isClustered: false  # å•节点模式(集群需改为true)
            clusterCheckinInterval: 10000
            txIsolationLevelSerializable: true
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10  # çº¿ç¨‹æ± å¤§å°
            threadPriority: 5
            makeThreadsDaemons: true
          updateCheck: false  # å…³é—­ç‰ˆæœ¬æ£€æŸ¥
# token配置
token:
  # ä»¤ç‰Œè‡ªå®šä¹‰æ ‡è¯†
  header: Authorization
  # ä»¤ç‰Œå¯†é’¥
  secret: abcdefghijklmnopqrstuvwxyz
  # ä»¤ç‰Œæœ‰æ•ˆæœŸï¼ˆé»˜è®¤30分钟)
  expireTime: 450
# MyBatis Plus配置
mybatis-plus:
  # æœç´¢æŒ‡å®šåŒ…别名   æ ¹æ®è‡ªå·±çš„项目来
  typeAliasesPackage: com.ruoyi.**.pojo
  # é…ç½®mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  # åŠ è½½å…¨å±€çš„é…ç½®æ–‡ä»¶
  configLocation: classpath:mybatis/mybatis-config.xml
  global-config:
    enable-sql-runner: true
    db-config:
      id-type: auto
# PageHelper分页插件
pagehelper:
  helperDialect: mysql
  supportMethodsArguments: true
  params: count=countSql
# Swagger配置
swagger:
  # æ˜¯å¦å¼€å¯swagger
  enabled: true
  # è¯·æ±‚前缀
  pathMapping: /dev-api
# é˜²æ­¢XSS攻击
xss:
  # è¿‡æ»¤å¼€å…³
  enabled: true
  # æŽ’除链接(多个用逗号分隔)
  excludes: /system/notice
  # åŒ¹é…é“¾æŽ¥
  urlPatterns: /system/*,/monitor/*,/tool/*
# ä»£ç ç”Ÿæˆ
gen:
  # ä½œè€…
  author: ruoyi
  # é»˜è®¤ç”ŸæˆåŒ…路径 system éœ€æ”¹æˆè‡ªå·±çš„æ¨¡å—名称 å¦‚ system monitor tool
  packageName: com.ruoyi.project.system
  # è‡ªåŠ¨åŽ»é™¤è¡¨å‰ç¼€ï¼Œé»˜è®¤æ˜¯true
  autoRemovePre: false
  # è¡¨å‰ç¼€ï¼ˆç”Ÿæˆç±»åä¸ä¼šåŒ…含表前缀,多个用逗号分隔)
  tablePrefix: sys_
  # æ˜¯å¦å…è®¸ç”Ÿæˆæ–‡ä»¶è¦†ç›–到本地(自定义路径),默认不允许
  allowOverwrite: false
file:
  temp-dir: /javaWork/product-inventory-management/file/temp/uploads
  upload-dir: /javaWork/product-inventory-management/file/prod/uploads
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/application-newTest.yml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,245 @@
# é¡¹ç›®ç›¸å…³é…ç½®
ruoyi:
  # åç§°
  name: RuoYi
  # ç‰ˆæœ¬
  version: 3.8.9
  # ç‰ˆæƒå¹´ä»½
  copyrightYear: 2025
  # æ–‡ä»¶è·¯å¾„ ç¤ºä¾‹ï¼ˆ Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
  profile: /javaWork/product-inventory-management/file
  # èŽ·å–ip地址开关
  addressEnabled: false
  # éªŒè¯ç ç±»åž‹ math æ•°å­—计算 char å­—符验证
  captchaType: math
  # ååŒå®¡æ‰¹ç¼–号前缀(配置文件后缀命名)
  approvalNumberPrefix: NEWTEST
# å¼€å‘环境配置
server:
  # æœåŠ¡å™¨çš„HTTP端口,默认为8080
  port: 9007
  servlet:
    # åº”用的访问路径
    context-path: /
  tomcat:
    # tomcat的URI编码
    uri-encoding: UTF-8
    # è¿žæŽ¥æ•°æ»¡åŽçš„æŽ’队数,默认为100
    accept-count: 1000
    threads:
      # tomcat最大线程数,默认为200
      max: 800
      # Tomcat启动初始化的线程数,默认值10
      min-spare: 100
# æ—¥å¿—配置
logging:
  level:
    com.ruoyi: warn
    org.springframework: warn
minio:
  endpoint: http://114.132.189.42/
  port: 7019
  secure: false
  accessKey: admin
  secretKey: 12345678
  preview-expiry: 24 # é¢„览地址默认24小时
  default-bucket: jxc
# ç”¨æˆ·é…ç½®
user:
  password:
    # å¯†ç æœ€å¤§é”™è¯¯æ¬¡æ•°
    maxRetryCount: 5
    # å¯†ç é”å®šæ—¶é—´ï¼ˆé»˜è®¤10分钟)
    lockTime: 10
# Spring配置
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: com.mysql.cj.jdbc.Driver
    druid:
      # ä¸»åº“数据源
      master:
        url: jdbc:mysql://172.17.0.1:3306/product-inventory-management-newtest?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
        username: root
        password: xd@123456..
      # ä»Žåº“数据源
      slave:
        # ä»Žæ•°æ®æºå¼€å…³/默认关闭
        enabled: false
        url:
        username:
        password:
      # åˆå§‹è¿žæŽ¥æ•°
      initialSize: 5
      # æœ€å°è¿žæŽ¥æ± æ•°é‡
      minIdle: 10
      # æœ€å¤§è¿žæŽ¥æ± æ•°é‡
      maxActive: 20
      # é…ç½®èŽ·å–è¿žæŽ¥ç­‰å¾…è¶…æ—¶çš„æ—¶é—´
      maxWait: 60000
      # é…ç½®è¿žæŽ¥è¶…æ—¶æ—¶é—´
      connectTimeout: 30000
      # é…ç½®ç½‘络超时时间
      socketTimeout: 60000
      # é…ç½®é—´éš”多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # é…ç½®ä¸€ä¸ªè¿žæŽ¥åœ¨æ± ä¸­æœ€å°ç”Ÿå­˜çš„æ—¶é—´ï¼Œå•位是毫秒
      minEvictableIdleTimeMillis: 300000
      # é…ç½®ä¸€ä¸ªè¿žæŽ¥åœ¨æ± ä¸­æœ€å¤§ç”Ÿå­˜çš„æ—¶é—´ï¼Œå•位是毫秒
      maxEvictableIdleTimeMillis: 900000
      # é…ç½®æ£€æµ‹è¿žæŽ¥æ˜¯å¦æœ‰æ•ˆ
      validationQuery: SELECT 1 FROM DUAL
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      webStatFilter:
        enabled: true
      statViewServlet:
        enabled: true
        # è®¾ç½®ç™½åå•,不填则允许所有访问
        allow:
        url-pattern: /druid/*
        # æŽ§åˆ¶å°ç®¡ç†ç”¨æˆ·åå’Œå¯†ç 
        login-username: ruoyi
        login-password: 123456
      filter:
        stat:
          enabled: true
          # æ…¢SQL记录
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true
  # èµ„源信息
  messages:
    # å›½é™…化资源文件路径
    basename: i18n/messages
  # æ–‡ä»¶ä¸Šä¼ 
  servlet:
    multipart:
      # å•个文件大小
      max-file-size: 1GB
      # è®¾ç½®æ€»ä¸Šä¼ çš„æ–‡ä»¶å¤§å°
      max-request-size: 2GB
  # æœåŠ¡æ¨¡å—
  devtools:
    restart:
      # çƒ­éƒ¨ç½²å¼€å…³
      enabled: false
  # redis é…ç½®
  redis:
    # åœ°å€
    #    host: 127.0.0.1
    host: 172.17.0.1
    # ç«¯å£ï¼Œé»˜è®¤ä¸º6379
    port: 6379
    # æ•°æ®åº“索引
    database: 6
    # å¯†ç 
    #    password: root2022!
    password:
    # è¿žæŽ¥è¶…æ—¶æ—¶é—´
    timeout: 10s
    lettuce:
      pool:
        # è¿žæŽ¥æ± ä¸­çš„æœ€å°ç©ºé—²è¿žæŽ¥
        min-idle: 0
        # è¿žæŽ¥æ± ä¸­çš„æœ€å¤§ç©ºé—²è¿žæŽ¥
        max-idle: 8
        # è¿žæŽ¥æ± çš„æœ€å¤§æ•°æ®åº“连接数
        max-active: 8
        # #连接池最大阻塞等待时间(使用负值表示没有限制)
        max-wait: -1ms
  # Quartz定时任务配置(新增部分)
  quartz:
    job-store-type: jdbc  # ä½¿ç”¨æ•°æ®åº“存储
    jdbc:
      initialize-schema: never  # é¦–次运行时自动创建表结构,成功后改为never
      schema: classpath:org/quartz/impl/jdbcjobstore/tables_mysql_innodb.sql  # MySQL表结构脚本
    properties:
      org:
        quartz:
          scheduler:
            instanceName: RuoYiScheduler
            instanceId: AUTO
          jobStore:
            class: org.quartz.impl.jdbcjobstore.JobStoreTX
            driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate  # MySQL适配
            tablePrefix: qrtz_  # è¡¨åå‰ç¼€ï¼Œä¸Žè„šæœ¬ä¸€è‡´
            isClustered: false  # å•节点模式(集群需改为true)
            clusterCheckinInterval: 10000
            txIsolationLevelSerializable: true
          threadPool:
            class: org.quartz.simpl.SimpleThreadPool
            threadCount: 10  # çº¿ç¨‹æ± å¤§å°
            threadPriority: 5
            makeThreadsDaemons: true
          updateCheck: false  # å…³é—­ç‰ˆæœ¬æ£€æŸ¥
# token配置
token:
  # ä»¤ç‰Œè‡ªå®šä¹‰æ ‡è¯†
  header: Authorization
  # ä»¤ç‰Œå¯†é’¥
  secret: abcdefghijklmnopqrstuvwxyz
  # ä»¤ç‰Œæœ‰æ•ˆæœŸï¼ˆé»˜è®¤30分钟)
  expireTime: 450
# MyBatis Plus配置
mybatis-plus:
  # æœç´¢æŒ‡å®šåŒ…别名   æ ¹æ®è‡ªå·±çš„项目来
  typeAliasesPackage: com.ruoyi.**.pojo
  # é…ç½®mapper的扫描,找到所有的mapper.xml映射文件
  mapperLocations: classpath*:mapper/**/*Mapper.xml
  # åŠ è½½å…¨å±€çš„é…ç½®æ–‡ä»¶
  configLocation: classpath:mybatis/mybatis-config.xml
  global-config:
    enable-sql-runner: true
    db-config:
      id-type: auto
# PageHelper分页插件
pagehelper:
  helperDialect: mysql
  supportMethodsArguments: true
  params: count=countSql
# Swagger配置
swagger:
  # æ˜¯å¦å¼€å¯swagger
  enabled: true
  # è¯·æ±‚前缀
  pathMapping: /dev-api
# é˜²æ­¢XSS攻击
xss:
  # è¿‡æ»¤å¼€å…³
  enabled: true
  # æŽ’除链接(多个用逗号分隔)
  excludes: /system/notice
  # åŒ¹é…é“¾æŽ¥
  urlPatterns: /system/*,/monitor/*,/tool/*
# ä»£ç ç”Ÿæˆ
gen:
  # ä½œè€…
  author: ruoyi
  # é»˜è®¤ç”ŸæˆåŒ…路径 system éœ€æ”¹æˆè‡ªå·±çš„æ¨¡å—名称 å¦‚ system monitor tool
  packageName: com.ruoyi.project.system
  # è‡ªåŠ¨åŽ»é™¤è¡¨å‰ç¼€ï¼Œé»˜è®¤æ˜¯true
  autoRemovePre: false
  # è¡¨å‰ç¼€ï¼ˆç”Ÿæˆç±»åä¸ä¼šåŒ…含表前缀,多个用逗号分隔)
  tablePrefix: sys_
  # æ˜¯å¦å…è®¸ç”Ÿæˆæ–‡ä»¶è¦†ç›–到本地(自定义路径),默认不允许
  allowOverwrite: false
file:
  temp-dir: /javaWork/product-inventory-management/file/temp/uploads
  upload-dir: /javaWork/product-inventory-management/file/prod/uploads
src/main/resources/mapper/account/AccountIncomeMapper.xml
@@ -76,7 +76,7 @@
               IFNULL(SUM(income_money), 0)            AS amount
        FROM account_income
        WHERE income_date BETWEEN #{startDate} AND #{endDate}
          AND business_type = 1
#           AND business_type = 1
        GROUP BY dateStr
        ORDER BY dateStr
src/main/resources/mapper/compensationperformance/CompensationPerformanceMapper.xml
@@ -3,14 +3,35 @@
<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>
    <select id="exportList" resultType="com.ruoyi.compensationperformance.pojo.CompensationPerformance">
        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
        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/ProductionProductInputMapper.xml
@@ -40,14 +40,39 @@
    <select id="selectInputStats" resultType="java.util.Map">
        SELECT
            DATE_FORMAT(create_time, '%Y-%m-%d') as date,
            SUM(quantity) as quantity
        FROM
            production_product_input
        WHERE
            create_time &gt;= #{startDate}
            AND create_time &lt;= #{endDate}
        DATE_FORMAT(ppi.create_time, '%Y-%m-%d') AS date,
        -- æŠ¥å·¥æ•°é‡ * æœ€å°ç³»æ•°
        SUM(ppi.quantity * IFNULL(distinct_ps.unit_quantity, 1)) AS quantity
        FROM production_product_input ppi
        INNER JOIN production_product_main ppm ON ppm.id = ppi.product_main_id
        INNER JOIN product_process_route_item ppri ON ppri.id = ppm.product_process_route_item_id
        INNER JOIN product_process_route ppr ON ppr.id = ppri.product_route_id
        INNER JOIN product_model pm ON pm.id = ppi.product_model_id
        INNER JOIN (
        SELECT
        bom_id,
        process_id,
        product_model_id,
        CAST(SUBSTRING_INDEX(GROUP_CONCAT(unit_quantity ORDER BY id ASC), ',', 1) AS DECIMAL(16, 4)) AS unit_quantity
        FROM product_structure
        GROUP BY bom_id, process_id, product_model_id
        ) distinct_ps ON distinct_ps.bom_id = ppr.bom_id
        AND distinct_ps.process_id = ppri.process_id
        AND distinct_ps.product_model_id = ppi.product_model_id
        <where>
            <if test="startDate != null">
                AND ppi.create_time &gt;= #{startDate}
            </if>
            <if test="endDate != null">
                AND ppi.create_time &lt;= #{endDate}
            </if>
        </where>
        GROUP BY
            DATE_FORMAT(create_time, '%Y-%m-%d')
        DATE_FORMAT(ppi.create_time, '%Y-%m-%d'),
        pm.id,
        pm.model
        ORDER BY
        date DESC,
        pm.id ASC
    </select>
</mapper>
src/main/resources/mapper/production/ProductionProductMainMapper.xml
@@ -18,6 +18,7 @@
        pwo.status as workOrderStatus,
        u.nick_name as nickName,
        p.product_name as productName,
        pp.name as process,
        pm.model as productModelName,
        ppo.quantity,
        ppo.scrap_qty,
@@ -26,6 +27,8 @@
        from
        production_product_main ppm
        left join product_work_order pwo on pwo.id = ppm.work_order_id
        left join product_process_route_item ppri on ppri.id = pwo.product_process_route_item_id
        left join product_process pp on pp.id = ppri.process_id
        left join product_order po on po.id = pwo.product_order_id
        left join production_product_output ppo on ppm.id = ppo.product_main_id
        left join product_model pm on pm.id = ppo.product_model_id
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/purchase/PurchaseLedgerMapper.xml
@@ -9,8 +9,9 @@
        SET contract_amount = #{totalTaxInclusiveAmount}
        WHERE id = #{id}
    </update>
    <select id="selectPurchaseLedgerListPage" resultType="com.ruoyi.purchase.dto.PurchaseLedgerDto">
        select
        SELECT
        pl.id,
        pl.purchase_contract_number ,
        pl.sales_contract_no,
@@ -18,8 +19,8 @@
        pl.supplier_name,
        pl.project_name,
        pl.contract_amount,
        IFNULL(sum(tr.invoice_amount),0) as receipt_payment_amount,
        pl.contract_amount-IFNULL(sum(tr.invoice_amount),0) AS unReceipt_payment_amount,
        IFNULL(tr_sum.total_invoice_amount, 0) AS receipt_payment_amount,
        pl.contract_amount - IFNULL(tr_sum.total_invoice_amount, 0) AS unReceipt_payment_amount,
        pl.entry_date,
        pl.execution_date,
        pl.recorder_id,
@@ -30,42 +31,41 @@
        pl.approval_status,
        pl.payment_method,
        pl.remarks
        from purchase_ledger pl
        left join sales_ledger_product slp on slp.sales_ledger_id = pl.id and slp.type=2
        left join product_record pr on pl.id = pr.purchase_ledger_id
        left join ticket_registration tr on tr.id = pr.ticket_registration_id
        left join supplier_manage sm on pl.supplier_id = sm.id
        FROM purchase_ledger pl
        LEFT JOIN (
        SELECT
        purchase_ledger_id,
        SUM(invoice_amount) AS total_invoice_amount
        FROM ticket_registration
        GROUP BY purchase_ledger_id
        ) tr_sum ON pl.id = tr_sum.purchase_ledger_id
        LEFT JOIN supplier_manage sm ON pl.supplier_id = sm.id
        <where>
            1 = 1
            <if test="c.purchaseContractNumber != null and c.purchaseContractNumber != ''">
               and pl.purchase_contract_number like concat('%',#{c.purchaseContractNumber},'%')
                AND pl.purchase_contract_number LIKE CONCAT('%', #{c.purchaseContractNumber}, '%')
            </if>
            <if test="c.approvalStatus != null and c.approvalStatus != ''">
                and pl.approval_status = #{c.approvalStatus}
                AND pl.approval_status = #{c.approvalStatus}
            </if>
            <if test="c.supplierName != null and c.supplierName != ''">
                and pl.supplier_name like concat('%',#{c.supplierName},'%')
                AND pl.supplier_name LIKE CONCAT('%', #{c.supplierName}, '%')
            </if>
            <if test="c.salesContractNo != null and c.salesContractNo != ''">
                and pl.sales_contract_no like concat('%',#{c.salesContractNo},'%')
                AND pl.sales_contract_no LIKE CONCAT('%', #{c.salesContractNo}, '%')
            </if>
            <if test="c.projectName != null and c.projectName != ''">
                and pl.project_name like concat('%',#{c.projectName},'%')
                AND pl.project_name LIKE CONCAT('%', #{c.projectName}, '%')
            </if>
            <if test="c.entryDateStart != null and c.entryDateStart != '' ">
                AND pl.entry_date &gt;= DATE_FORMAT(#{c.entryDateStart},'%Y-%m-%d')
                AND pl.entry_date &gt;= #{c.entryDateStart}
            </if>
            <if test="c.entryDateEnd != null and c.entryDateEnd != '' ">
                AND  pl.entry_date &lt;= DATE_FORMAT(#{c.entryDateEnd},'%Y-%m-%d')
                AND pl.entry_date &lt;= #{c.entryDateEnd}
            </if>
        </where>
        group by pl.id, pl.purchase_contract_number, pl.sales_contract_no, pl.supplier_name,
        pl.project_name,pl.entry_date,
        pl.recorder_name,
        pl.contract_amount
        order by pl.entry_date desc
        ORDER BY pl.entry_date DESC
    </select>
    <select id="getPaymentRegistrationDtoById" resultType="com.ruoyi.purchase.dto.PaymentRegistrationDto">
        SELECT
            T1.id,
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,40 @@
            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>
    <select id="getModelId" resultType="java.lang.Long">
        select pm.id
        from product_model pm
        left join product p on pm.product_id=p.id
        where pm.model=#{model}
          and  p.product_name=#{productName}
    </select>
</mapper>
src/main/resources/mapper/sales/SalesLedgerProductMapper.xml
@@ -8,23 +8,23 @@
        SELECT
        T1.*,
        CASE
        WHEN t2.qualitity > T1.quantity THEN 1
        WHEN (IFNULL(t2.qualitity, 0) - IFNULL(t2.locked_quantity, 0)) >= IFNULL(T1.quantity, 0) THEN 1
        ELSE 0
        END as has_sufficient_stock
        FROM
        sales_ledger_product T1
        LEFT JOIN stock_inventory t2 ON T1.product_model_id = t2.product_model_id
        <where>
            1=1
            <if test="salesLedgerProduct.salesLedgerId != null and salesLedgerProduct.salesLedgerId != '' ">
            <if test="salesLedgerProduct.salesLedgerId != null">
                AND T1.sales_ledger_id = #{salesLedgerProduct.salesLedgerId}
            </if>
            <if test="salesLedgerProduct.type != null and salesLedgerProduct.type != '' ">
            <if test="salesLedgerProduct.type != null">
                AND T1.type = #{salesLedgerProduct.type}
            </if>
        </where>
        ORDER BY T1.register_date DESC
    </select>
    <select id="selectSalesLedgerProductByMainId" resultType="com.ruoyi.sales.pojo.SalesLedgerProduct">
        select slp.*
        from quality_inspect qi
@@ -196,11 +196,15 @@
    </select>
    <select id="selectProductCountByTypeAndDate" resultType="int">
        SELECT COUNT(*)
        SELECT IFNULL(COUNT(*), 0)
        FROM sales_ledger_product
        WHERE type = #{type}
        <if test="startDate != null">
        AND register_date &gt;= #{startDate}
        </if>
        <if test="endDate != null">
        AND register_date &lt;= #{endDate}
        </if>
    </select>
    <select id="selectRawMaterialExpense" resultType="java.math.BigDecimal">
src/main/resources/mapper/staff/PersonalAttendanceLocationConfigMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
<?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.PersonalAttendanceLocationConfigMapper">
    <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.staff.pojo.PersonalAttendanceLocationConfig">
        <id column="id" property="id" />
        <result column="sys_dept_id" property="sysDeptId" />
        <result column="location_name" property="locationName" />
        <result column="longitude" property="longitude" />
        <result column="latitude" property="latitude" />
        <result column="radius" property="radius" />
        <result column="start_at" property="startAt" />
        <result column="end_at" property="endAt" />
    </resultMap>
</mapper>
src/main/resources/mapper/staff/PersonalAttendanceRecordsMapper.xml
@@ -2,6 +2,71 @@
<!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>
    <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.staffOnJobId != null and params.staffOnJobId > 0">
            AND personal_attendance_records.staff_on_job_id = #{params.staffOnJobId}
        </if>
        <if test="params.deptId != null and params.deptId > 0">
            AND sd.dept_id = #{params.deptId}
        </if>
        <if test="params.date != null">
            and personal_attendance_records.date &gt;= #{params.date}
            and personal_attendance_records.date &lt; DATE_ADD(DATE(#{params.date}), INTERVAL 1 DAY)
        </if>
    </select>
    <!-- æŸ¥è¯¢æŒ‡å®šæ—¥æœŸæ²¡æœ‰è€ƒå‹¤è®°å½•的在职员工(在指定时间之前入职的) -->
    <select id="selectStaffWithoutAttendanceRecordBeforeTime" resultType="com.ruoyi.staff.pojo.StaffOnJob">
        SELECT soj.*
        FROM staff_on_job soj
        WHERE soj.staff_state = 1
        AND soj.create_time &lt; #{entryDeadline}
        AND EXISTS (
        SELECT 1
        FROM personal_attendance_location_config palc
        WHERE palc.sys_dept_id = soj.sys_dept_id
        )
        AND NOT EXISTS (
        SELECT 1
        FROM personal_attendance_records par
        WHERE par.staff_on_job_id = soj.id
        AND par.date = #{date}
        )
    </select>
    <!-- æ£€æŸ¥æŒ‡å®šå‘˜å·¥åœ¨æŒ‡å®šæ—¥æœŸæ˜¯å¦å·²å­˜åœ¨è€ƒå‹¤è®°å½• -->
    <select id="existsAttendanceRecord" resultType="boolean">
        SELECT EXISTS (
        SELECT 1
        FROM personal_attendance_records
        WHERE staff_on_job_id = #{staffOnJobId}
        AND date = #{date}
        )
    </select>
</mapper>
src/main/resources/mapper/staff/StaffOnJobMapper.xml
@@ -57,5 +57,22 @@
        FROM staff_on_job
        WHERE staff_state = 1
        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/stock/StockInventoryMapper.xml
@@ -199,14 +199,16 @@
    </select>
    <select id="selectStorageProductCountByDate" resultType="int">
        SELECT COUNT(*)
        FROM (SELECT create_time
        SELECT SUM(total_count)
        FROM (SELECT COUNT(*) as total_count
              FROM stock_inventory
              UNION ALL
              SELECT create_time
              FROM stock_uninventory) combined
        WHERE create_time &gt;= #{startDate}
          AND create_time &lt;= #{endDate}
              UNION ALL
              SELECT COUNT(*) as total_count
              FROM stock_uninventory
              WHERE create_time &gt;= #{startDate}
                AND create_time &lt;= #{endDate}) AS combined_counts
    </select>
    <select id="selectDailyStockInCounts" resultType="java.util.Map">
src/main/resources/mapper/system/SysMenuMapper.xml
@@ -134,6 +134,13 @@
        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>
src/main/resources/mapper/system/SysNoticeMapper.xml
@@ -31,6 +31,7 @@
               sender_id,
               consignee_id,
               jump_path,
               app_jump_path,
               tenant_id
        from sys_notice
    </sql>
@@ -75,6 +76,7 @@
        <if test="senderId != null and senderId != ''">sender_id,</if>
        <if test="consigneeId != null and consigneeId != ''">consignee_id,</if>
        <if test="jumpPath != null and jumpPath != ''">jump_path,</if>
        <if test="appJumpPath != null and appJumpPath != ''">app_jump_path,</if>
        <if test="createBy != null and createBy != ''">create_by,</if>
        <if test="tenantId != null and tenantId != ''">tenant_id,</if>
             create_time
@@ -87,6 +89,7 @@
        <if test="senderId != null and senderId != ''">#{senderId},</if>
        <if test="consigneeId != null and consigneeId != ''">#{consigneeId},</if>
        <if test="jumpPath != null and jumpPath != ''">#{jumpPath},</if>
        <if test="appJumpPath != null and appJumpPath != ''">#{appJumpPath},</if>
        <if test="pathParms != null and pathParms != ''">#{queryParms},</if>
        <if test="createBy != null and createBy != ''">#{createBy},</if>
        <if test="tenantId != null and tenantId != ''">#{tenantId},</if>