6 天以前 d17d4d1ac98865d2a71b4f24771b7b76e0839ed3
yys
1.设备区域
2.设备巡检,维保,保养多选
3.数采
已添加11个文件
已修改22个文件
1970 ■■■■■ 文件已修改
sql/device_area.sql 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/device_records_add_area_id.sql 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/timing_task_add_task_ids_str.sql 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/controller/DeviceAreaController.java 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/dto/DeviceAreaTreeDto.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java 7 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/dto/DeviceRepairDto.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/execl/DeviceLedgerExeclDto.java 93 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/mapper/DeviceAreaMapper.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/DeviceArea.java 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/DeviceLedger.java 64 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/DeviceMaintenance.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/DeviceRepair.java 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/IDeviceAreaService.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/DeviceAreaServiceImpl.java 194 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java 99 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java 179 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java 95 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/http/service/controller/JclyController.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/http/service/impl/RealTimeEnergyConsumptionServiceImpl.java 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java 197 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java 266 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/device/DeviceAreaMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/device/DeviceLedgerMapper.xml 47 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/device/DeviceMaintenanceMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/device/DeviceRepairMapper.xml 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/device_area.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
CREATE TABLE `device_area` (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `area_name` varchar(100) NOT NULL COMMENT '区域名称',
  `parent_id` bigint DEFAULT NULL COMMENT '父级ID',
  `sort` bigint DEFAULT 0 COMMENT '排序',
  `remark` varchar(255) DEFAULT NULL COMMENT '备注',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `create_user` int DEFAULT NULL COMMENT '创建用户',
  `update_user` int DEFAULT NULL COMMENT '更新用户',
  `tenant_id` bigint DEFAULT NULL COMMENT '租户ID',
  `dept_id` bigint DEFAULT NULL COMMENT '部门ID',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备区域';
sql/device_records_add_area_id.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
ALTER TABLE `device_maintenance`
    ADD COLUMN `area_id` bigint DEFAULT NULL COMMENT '设备区域id' AFTER `device_ledger_id`;
ALTER TABLE `device_repair`
    ADD COLUMN `area_id` bigint DEFAULT NULL COMMENT '设备区域id' AFTER `device_ledger_id`;
ALTER TABLE `inspection_task`
    ADD COLUMN `area_id` bigint DEFAULT NULL COMMENT '设备区域id' AFTER `task_id`;
ALTER TABLE `timing_task`
    ADD COLUMN `area_id` bigint DEFAULT NULL COMMENT '设备区域id' AFTER `task_id`;
ALTER TABLE `maintenance_task`
    ADD COLUMN `area_id` bigint DEFAULT NULL COMMENT '设备区域id' AFTER `task_id`;
sql/timing_task_add_task_ids_str.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,2 @@
ALTER TABLE `timing_task`
    ADD COLUMN `task_ids_str` varchar(1000) DEFAULT NULL COMMENT '设备ID集合' AFTER `task_id`;
src/main/java/com/ruoyi/device/controller/DeviceAreaController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,69 @@
package com.ruoyi.device.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.device.pojo.DeviceArea;
import com.ruoyi.device.service.IDeviceAreaService;
import com.ruoyi.framework.aspectj.lang.annotation.Log;
import com.ruoyi.framework.aspectj.lang.enums.BusinessType;
import com.ruoyi.framework.web.domain.AjaxResult;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(tags = "设备区域管理")
@RestController
@RequestMapping("/device/area")
public class DeviceAreaController {
    @Autowired
    private IDeviceAreaService deviceAreaService;
    @GetMapping("/tree")
    @ApiOperation("设备区域树")
    public AjaxResult tree() {
        return AjaxResult.success(deviceAreaService.listTree());
    }
    @GetMapping("/treeWithDevices")
    @ApiOperation("设备区域树-包含设备列表")
    public AjaxResult treeWithDevices() {
        return AjaxResult.success(deviceAreaService.listTreeWithDevices());
    }
    @GetMapping("/page")
    @ApiOperation("设备区域分页")
    public AjaxResult page(Page page, DeviceArea deviceArea) {
        return AjaxResult.success(deviceAreaService.queryPage(page, deviceArea));
    }
    @GetMapping("/{id}")
    @ApiOperation("设备区域详情")
    public AjaxResult detail(@PathVariable Long id) {
        return AjaxResult.success(deviceAreaService.getById(id));
    }
    @PostMapping
    @ApiOperation("新增设备区域")
    @Log(title = "设备区域", businessType = BusinessType.INSERT)
    public AjaxResult add(@RequestBody DeviceArea deviceArea) {
        return deviceAreaService.saveDeviceArea(deviceArea);
    }
    @PutMapping
    @ApiOperation("修改设备区域")
    @Log(title = "设备区域", businessType = BusinessType.UPDATE)
    public AjaxResult update(@RequestBody DeviceArea deviceArea) {
        return deviceAreaService.updateDeviceArea(deviceArea);
    }
    @DeleteMapping
    @ApiOperation("删除设备区域")
    @Log(title = "设备区域", businessType = BusinessType.DELETE)
    public AjaxResult delete(@RequestBody List<Long> ids) {
        return deviceAreaService.removeDeviceAreas(ids);
    }
}
src/main/java/com/ruoyi/device/dto/DeviceAreaTreeDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.ruoyi.device.dto;
import com.ruoyi.device.pojo.DeviceLedger;
import lombok.Data;
import java.util.List;
@Data
public class DeviceAreaTreeDto {
    private Long id;
    private Long parentId;
    private String areaName;
    private String label;
    private Long sort;
    private String remark;
    private List<DeviceLedger> deviceList;
    private List<DeviceAreaTreeDto> children;
}
src/main/java/com/ruoyi/device/dto/DeviceLedgerDto.java
@@ -13,106 +13,56 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * è®¾å¤‡å°è´¦å®žä½“ç±»
 */
@Data
@TableName("device_ledger")
public class DeviceLedgerDto extends DateQueryDto {
    /**
     * ä¸»é”®ID,自增
     */
    private Long id;
    /**
     * è®¾å¤‡åç§°
     */
    private String deviceName;
    /**
     * è§„格型号
     */
    private String deviceModel;
    /**
     * è®¾å¤‡å“ç‰Œ
     */
    @ApiModelProperty("设备品牌")
    private String deviceBrand;
    /**
     * å­˜æ”¾ä½ç½®
     */
    @ApiModelProperty("存放位置")
    private String storageLocation;
    @ApiModelProperty("设备区域ID")
    private Long areaId;
    /**
     * ä¾›åº”商名称
     */
    @ApiModelProperty("设备区域名称")
    private String areaName;
    private String supplierName;
    /**
     * å•位
     */
    private String unit;
    /**
     * æ•°é‡
     */
    private BigDecimal number;
    /**
     * å«ç¨Žå•ä»·
     */
    private BigDecimal taxIncludingPriceUnit;
    /**
     * å«ç¨Žæ€»ä»·
     */
    private BigDecimal taxIncludingPriceTotal;
    /**
     * ç¨Žçއ
     */
    private BigDecimal taxRate;
    /**
     * ä¸å«ç¨Žæ€»ä»·
     */
    private BigDecimal unTaxIncludingPriceTotal;
    /**
     * å½•入时间
     */
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    /**
     * æ›´æ–°æ—¶é—´
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    /**
     * å½•入人
     */
    private String createUser;
    /**
     * æ›´æ–°äºº
     */
    private String updateUser;
    /**
     * ç§Ÿæˆ·ID
     */
    private Long tenantId;
    @ApiModelProperty("状态")
@@ -135,7 +85,6 @@
    @ApiModelProperty("运行时长")
    private String runtimeDuration;
    @ApiModelProperty("是否折旧 1-是 2-否")
    private Integer isDepr;
src/main/java/com/ruoyi/device/dto/DeviceMaintenanceDto.java
@@ -10,13 +10,18 @@
@Data
public class DeviceMaintenanceDto {
    @ApiModelProperty("设备保养id")
    private Long id;
    @ApiModelProperty("设备台账id")
    private Long deviceLedgerId;
    @ApiModelProperty("设备区域id")
    private Long areaId;
    @ApiModelProperty("设备区域名称")
    private String areaName;
    @ApiModelProperty("设备名称")
    private String deviceName;
src/main/java/com/ruoyi/device/dto/DeviceRepairDto.java
@@ -1,6 +1,5 @@
package com.ruoyi.device.dto;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModelProperty;
@@ -17,6 +16,12 @@
    @ApiModelProperty("设备台账id")
    private Long deviceLedgerId;
    @ApiModelProperty("设备区域id")
    private Long areaId;
    @ApiModelProperty("设备区域名称")
    private String areaName;
    @ApiModelProperty("设备名称")
    private String deviceName;
@@ -68,7 +73,4 @@
    @ApiModelProperty("租户id")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
}
src/main/java/com/ruoyi/device/execl/DeviceLedgerExeclDto.java
@@ -7,24 +7,40 @@
import org.springframework.format.annotation.DateTimeFormat;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.Date;
@Data
public class DeviceLedgerExeclDto {
    @Excel(name = "设备类型", sort = 0, combo = {"生产设备", "办公设备", "检测设备", "运输设备", "其他设备"})
    private String type;
    /**
     * è®¾å¤‡åç§°
     */
    @Excel(name = "设备名称" ,sort = 1)
    private String deviceName;
    /**
     * è®¾å¤‡ç±»åž‹
     */
    @Excel(name = "设备类型",sort = 0,combo = {"生产设备","办公设备","检查设备","运输设备","其他设备"})
    private String type;
    @Excel(name = "规格型号", sort = 2)
    private String deviceModel;
    @Excel(name = "供应商名称", sort = 3)
    private String supplierName;
    @Excel(name = "单位", sort = 4)
    private String unit;
    @Excel(name = "数量", sort = 5, type = Excel.Type.EXPORT)
    private BigDecimal number = BigDecimal.ONE;
    @Excel(name = "含税单价", sort = 6)
    private BigDecimal taxIncludingPriceUnit;
    @Excel(name = "含税总价", sort = 7, type = Excel.Type.EXPORT)
    private BigDecimal taxIncludingPriceTotal;
    @Excel(name = "税率", sort = 8)
    private BigDecimal taxRate;
    @Excel(name = "不含税总价", sort = 9, type = Excel.Type.EXPORT)
    private BigDecimal unTaxIncludingPriceTotal;
    @ApiModelProperty("计划运行时间")
    @JsonFormat(pattern = "yyyy-MM-dd")
@@ -32,62 +48,9 @@
    @Excel(name = "计划运行时间",sort = 10,dateFormat = "yyyy-MM-dd")
    private Date planRuntimeTime;
    /**
     * è§„格型号
     */
    @Excel(name = "规格型号" ,sort = 2)
    private String deviceModel;
    @Excel(name = "设备区域", sort = 11)
    private String areaName;
    /**
     * ä¾›åº”商名称
     */
    @Excel(name = "供应商名称",sort = 3)
    private String supplierName;
    /**
     * å•位
     */
    @Excel(name = "单位",sort = 4)
    private String unit;
    /**
     * æ•°é‡
     */
    @Excel(name = "数量",sort = 5, type = Excel.Type.EXPORT)
    private BigDecimal number = BigDecimal.ONE;
    /**
     * å«ç¨Žå•ä»·
     */
    @Excel(name = "含税单价",sort = 6)
    private BigDecimal taxIncludingPriceUnit;
    /**
     * å«ç¨Žæ€»ä»·
     */
    @Excel(name = "含税总价",sort = 7, type = Excel.Type.EXPORT)
    private BigDecimal taxIncludingPriceTotal;
    /**
     * ç¨Žçއ
     */
    @Excel(name = "税率",sort = 8)
    private BigDecimal taxRate;
    /**
     * ä¸å«ç¨Žæ€»ä»·
     */
    @Excel(name = "不含税总价",sort = 9, type = Excel.Type.EXPORT)
    private BigDecimal unTaxIncludingPriceTotal;
    /**
     * å½•入人
     */
    @Excel(name = "录入人",sort = 9)
    @Excel(name = "录入人", sort = 12)
    private String createUser;
}
src/main/java/com/ruoyi/device/mapper/DeviceAreaMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,14 @@
package com.ruoyi.device.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.device.pojo.DeviceArea;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface DeviceAreaMapper extends BaseMapper<DeviceArea> {
    IPage<DeviceArea> queryPage(Page page, @Param("deviceArea") DeviceArea deviceArea);
}
src/main/java/com/ruoyi/device/pojo/DeviceArea.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,61 @@
package com.ruoyi.device.pojo;
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 io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("device_area")
@ApiModel("设备区域")
public class DeviceArea {
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    @ApiModelProperty("区域名称")
    private String areaName;
    @ApiModelProperty("父级ID")
    private Long parentId;
    @ApiModelProperty("排序")
    private Long sort;
    @ApiModelProperty("备注")
    private String remark;
    @ApiModelProperty("创建时间")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("创建用户")
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty("更新用户")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty("租户ID")
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    @ApiModelProperty("部门ID")
    @TableField(fill = FieldFill.INSERT)
    private Long deptId;
    @TableField(exist = false)
    @ApiModelProperty("父级名称")
    private String parentName;
}
src/main/java/com/ruoyi/device/pojo/DeviceLedger.java
@@ -13,114 +13,64 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * è®¾å¤‡å°è´¦å®žä½“ç±»
 */
@Data
@TableName("device_ledger")
@ApiModel
public class DeviceLedger {
    /**
     * ä¸»é”®ID,自增
     */
    private Long id;
    /**
     * è®¾å¤‡åç§°
     */
    @ApiModelProperty("设备名称")
    private String deviceName;
    /**
     * è§„格型号
     */
    @ApiModelProperty("规格型号")
    private String deviceModel;
    /**
     * è®¾å¤‡å“ç‰Œ
     */
    @ApiModelProperty("设备品牌")
    private String deviceBrand;
    /**
     * å­˜æ”¾ä½ç½®
     */
    @ApiModelProperty("存放位置")
    private String storageLocation;
    /**
     * ä¾›åº”商名称
     */
    @ApiModelProperty("设备区域ID")
    private Long areaId;
    @TableField(exist = false)
    @ApiModelProperty("设备区域名称")
    private String areaName;
    private String supplierName;
    /**
     * å•位
     */
    private String unit;
    /**
     * æ•°é‡
     */
    private BigDecimal number;
    /**
     * å«ç¨Žå•ä»·
     */
    private BigDecimal taxIncludingPriceUnit;
    /**
     * å«ç¨Žæ€»ä»·
     */
    private BigDecimal taxIncludingPriceTotal;
    /**
     * ç¨Žçއ
     */
    private BigDecimal taxRate;
    /**
     * ä¸å«ç¨Žæ€»ä»·
     */
    private BigDecimal unTaxIncludingPriceTotal;
    /**
     * å½•入时间
     */
    @TableField(fill = FieldFill.INSERT)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime createTime;
    /**
     * æ›´æ–°æ—¶é—´
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private LocalDateTime updateTime;
    /**
     * å½•入人
     */
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    /**
     * æ›´æ–°äºº
     */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    /**
     * ç§Ÿæˆ·ID
     */
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
    /* ***************************     è¿è¡Œç®¡ç†        ***************************   */
    @ApiModelProperty("状态")
    private String status;
src/main/java/com/ruoyi/device/pojo/DeviceMaintenance.java
@@ -23,7 +23,14 @@
    @ApiModelProperty("设备台账id")
    private Long deviceLedgerId;
    @ApiModelProperty("保养任务id")
    @ApiModelProperty("设备区域id")
    private Long areaId;
    @TableField(exist = false)
    @ApiModelProperty("设备台账ID集合")
    private Long[] deviceLedgerIds;
    @ApiModelProperty("设备保养任务id")
    private Long maintenanceTaskId;
    @ApiModelProperty(value = "频次")
@@ -37,7 +44,6 @@
    @ApiModelProperty(value = "最后执行时间")
    private LocalDateTime lastExecutionTime;
    private String deviceName;
@@ -69,9 +75,9 @@
    private LocalDateTime createTime;
    @ApiModelProperty("更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty("创建人")
src/main/java/com/ruoyi/device/pojo/DeviceRepair.java
@@ -22,6 +22,13 @@
    @ApiModelProperty("设备台账id")
    private Long deviceLedgerId;
    @ApiModelProperty("设备区域id")
    private Long areaId;
    @TableField(exist = false)
    @ApiModelProperty("设备台账ID集合")
    private Long[] deviceLedgerIds;
    private String deviceName;
    private String deviceModel;
src/main/java/com/ruoyi/device/pojo/MaintenanceTask.java
@@ -1,5 +1,6 @@
package com.ruoyi.device.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -14,10 +15,6 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
 * @author :yys
 * @date : 2025/9/19 10:27
 */
@Data
@ApiModel
@TableName("maintenance_task")
@@ -28,9 +25,6 @@
    @ApiModelProperty(value = "规格型号")
    private String deviceModel;
    /**
     * ä¸»é”®ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;
@@ -40,6 +34,20 @@
    @ApiModelProperty(value = "设备id")
    private Long taskId;
    @ApiModelProperty(value = "设备区域id")
    private Long areaId;
    @TableField(exist = false)
    @ApiModelProperty(value = "设备区域名称")
    private String areaName;
    @TableField(exist = false)
    @ApiModelProperty(value = "设备ID集合")
    private Long[] deviceLedgerIds;
    @ApiModelProperty(value = "设备ID集合字符串")
    private String deviceLedgerIdsStr;
    @ApiModelProperty(value = "频次")
    @Excel(name = "频次")
@@ -78,32 +86,29 @@
    @ApiModelProperty(value = "状态")
    private String status;
    @ApiModelProperty(value = "软删除标志,0=未删除,1=已删除")
    @ApiModelProperty(value = "软删除标记,0=未删除,1=已删除")
    private Integer deleted;
    @TableField(exist = false)
    private String dateStr;
    @ApiModelProperty(value = "创建该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "记录创建时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
//    @JsonFormat(pattern = "yyyy-MM-dd")
//    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty(value = "最后修改该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "记录最后更新时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "租户ID")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
}
src/main/java/com/ruoyi/device/service/IDeviceAreaService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
package com.ruoyi.device.service;
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.device.dto.DeviceAreaTreeDto;
import com.ruoyi.device.pojo.DeviceArea;
import com.ruoyi.framework.web.domain.AjaxResult;
import java.util.List;
public interface IDeviceAreaService extends IService<DeviceArea> {
    IPage<DeviceArea> queryPage(Page page, DeviceArea deviceArea);
    List<DeviceAreaTreeDto> listTree();
    List<DeviceAreaTreeDto> listTreeWithDevices();
    AjaxResult saveDeviceArea(DeviceArea deviceArea);
    AjaxResult updateDeviceArea(DeviceArea deviceArea);
    AjaxResult removeDeviceAreas(List<Long> ids);
}
src/main/java/com/ruoyi/device/service/impl/DeviceAreaServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,194 @@
package com.ruoyi.device.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.device.dto.DeviceAreaTreeDto;
import com.ruoyi.device.mapper.DeviceAreaMapper;
import com.ruoyi.device.mapper.DeviceLedgerMapper;
import com.ruoyi.device.pojo.DeviceArea;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.device.service.IDeviceAreaService;
import com.ruoyi.framework.web.domain.AjaxResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class DeviceAreaServiceImpl extends ServiceImpl<DeviceAreaMapper, DeviceArea> implements IDeviceAreaService {
    @Autowired
    private DeviceAreaMapper deviceAreaMapper;
    @Autowired
    private DeviceLedgerMapper deviceLedgerMapper;
    @Override
    public IPage<DeviceArea> queryPage(Page page, DeviceArea deviceArea) {
        return deviceAreaMapper.queryPage(page, deviceArea);
    }
    @Override
    public List<DeviceAreaTreeDto> listTree() {
        return buildTree(Collections.emptyMap());
    }
    @Override
    public List<DeviceAreaTreeDto> listTreeWithDevices() {
        LambdaQueryWrapper<DeviceLedger> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.isNotNull(DeviceLedger::getAreaId);
        queryWrapper.orderByAsc(DeviceLedger::getId);
        List<DeviceLedger> deviceLedgers = deviceLedgerMapper.selectList(queryWrapper);
        Map<Long, List<DeviceLedger>> deviceMap = new HashMap<>();
        for (DeviceLedger deviceLedger : deviceLedgers) {
            deviceMap.computeIfAbsent(deviceLedger.getAreaId(), key -> new ArrayList<>()).add(deviceLedger);
        }
        return buildTree(deviceMap);
    }
    @Override
    public AjaxResult saveDeviceArea(DeviceArea deviceArea) {
        if (deviceArea == null || !StringUtils.hasText(deviceArea.getAreaName())) {
            return AjaxResult.error("区域名称不能为空");
        }
        normalizeDeviceArea(deviceArea);
        if (existsSameName(deviceArea.getParentId(), deviceArea.getAreaName(), null)) {
            return AjaxResult.error("同级区域名称已存在");
        }
        if (deviceArea.getParentId() != null && deviceAreaMapper.selectById(deviceArea.getParentId()) == null) {
            return AjaxResult.error("父级区域不存在");
        }
        return this.save(deviceArea) ? AjaxResult.success() : AjaxResult.error();
    }
    @Override
    public AjaxResult updateDeviceArea(DeviceArea deviceArea) {
        if (deviceArea == null || deviceArea.getId() == null) {
            return AjaxResult.error("区域ID不能为空");
        }
        if (!StringUtils.hasText(deviceArea.getAreaName())) {
            return AjaxResult.error("区域名称不能为空");
        }
        normalizeDeviceArea(deviceArea);
        DeviceArea current = deviceAreaMapper.selectById(deviceArea.getId());
        if (current == null) {
            return AjaxResult.error("区域不存在");
        }
        if (deviceArea.getParentId() != null && deviceArea.getId().equals(deviceArea.getParentId())) {
            return AjaxResult.error("父级区域不能选择自身");
        }
        if (isDescendant(deviceArea.getId(), deviceArea.getParentId())) {
            return AjaxResult.error("父级区域不能选择子节点");
        }
        if (deviceArea.getParentId() != null && deviceAreaMapper.selectById(deviceArea.getParentId()) == null) {
            return AjaxResult.error("父级区域不存在");
        }
        if (existsSameName(deviceArea.getParentId(), deviceArea.getAreaName(), deviceArea.getId())) {
            return AjaxResult.error("同级区域名称已存在");
        }
        return this.updateById(deviceArea) ? AjaxResult.success() : AjaxResult.error();
    }
    @Override
    public AjaxResult removeDeviceAreas(List<Long> ids) {
        if (CollectionUtils.isEmpty(ids)) {
            return AjaxResult.error("请选择要删除的区域");
        }
        LambdaQueryWrapper<DeviceArea> childWrapper = new LambdaQueryWrapper<>();
        childWrapper.in(DeviceArea::getParentId, ids);
        if (deviceAreaMapper.selectCount(childWrapper) > 0) {
            return AjaxResult.error("存在子区域,不能直接删除");
        }
        return this.removeBatchByIds(ids) ? AjaxResult.success() : AjaxResult.error();
    }
    private List<DeviceAreaTreeDto> buildTree(Map<Long, List<DeviceLedger>> deviceMap) {
        LambdaQueryWrapper<DeviceArea> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.and(wrapper -> wrapper.isNull(DeviceArea::getParentId).or().eq(DeviceArea::getParentId, 0L));
        queryWrapper.orderByAsc(DeviceArea::getSort).orderByAsc(DeviceArea::getId);
        List<DeviceArea> rootList = deviceAreaMapper.selectList(queryWrapper);
        List<DeviceAreaTreeDto> tree = new ArrayList<>();
        for (DeviceArea deviceArea : rootList) {
            DeviceAreaTreeDto node = toTreeDto(deviceArea, deviceMap);
            node.setChildren(buildChildren(deviceArea.getId(), deviceMap));
            tree.add(node);
        }
        return tree;
    }
    private void normalizeDeviceArea(DeviceArea deviceArea) {
        if (deviceArea.getParentId() != null && deviceArea.getParentId() == 0L) {
            deviceArea.setParentId(null);
        }
        if (deviceArea.getSort() == null) {
            deviceArea.setSort(0L);
        }
    }
    private boolean existsSameName(Long parentId, String areaName, Long excludeId) {
        LambdaQueryWrapper<DeviceArea> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(DeviceArea::getAreaName, areaName);
        if (parentId == null) {
            queryWrapper.and(wrapper -> wrapper.isNull(DeviceArea::getParentId).or().eq(DeviceArea::getParentId, 0L));
        } else {
            queryWrapper.eq(DeviceArea::getParentId, parentId);
        }
        if (excludeId != null) {
            queryWrapper.ne(DeviceArea::getId, excludeId);
        }
        return deviceAreaMapper.selectCount(queryWrapper) > 0;
    }
    private boolean isDescendant(Long currentId, Long parentId) {
        if (currentId == null || parentId == null) {
            return false;
        }
        Long cursor = parentId;
        while (cursor != null && cursor != 0L) {
            if (currentId.equals(cursor)) {
                return true;
            }
            DeviceArea parent = deviceAreaMapper.selectById(cursor);
            if (parent == null) {
                return false;
            }
            cursor = parent.getParentId();
        }
        return false;
    }
    private List<DeviceAreaTreeDto> buildChildren(Long parentId, Map<Long, List<DeviceLedger>> deviceMap) {
        LambdaQueryWrapper<DeviceArea> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(DeviceArea::getParentId, parentId);
        queryWrapper.orderByAsc(DeviceArea::getSort).orderByAsc(DeviceArea::getId);
        List<DeviceArea> children = deviceAreaMapper.selectList(queryWrapper);
        List<DeviceAreaTreeDto> result = new ArrayList<>();
        for (DeviceArea child : children) {
            DeviceAreaTreeDto node = toTreeDto(child, deviceMap);
            node.setChildren(buildChildren(child.getId(), deviceMap));
            result.add(node);
        }
        return result;
    }
    private DeviceAreaTreeDto toTreeDto(DeviceArea deviceArea, Map<Long, List<DeviceLedger>> deviceMap) {
        DeviceAreaTreeDto dto = new DeviceAreaTreeDto();
        BeanUtils.copyProperties(deviceArea, dto);
        dto.setLabel(deviceArea.getAreaName());
        dto.setDeviceList(new ArrayList<>(deviceMap.getOrDefault(deviceArea.getId(), Collections.emptyList())));
        dto.setChildren(new ArrayList<>());
        return dto;
    }
}
src/main/java/com/ruoyi/device/service/impl/DeviceMaintenanceServiceImpl.java
@@ -1,6 +1,5 @@
package com.ruoyi.device.service.impl;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
@@ -9,7 +8,9 @@
import com.ruoyi.device.dto.DeviceMaintenanceDto;
import com.ruoyi.device.execl.DeviceMaintenanceExeclDto;
import com.ruoyi.device.mapper.DeviceMaintenanceMapper;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.service.IDeviceLedgerService;
import com.ruoyi.device.service.IDeviceMaintenanceService;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.measuringinstrumentledger.mapper.SparePartsMapper;
@@ -24,13 +25,11 @@
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Service
public class DeviceMaintenanceServiceImpl extends ServiceImpl<DeviceMaintenanceMapper, DeviceMaintenance> implements IDeviceMaintenanceService {
    @Autowired
    private DeviceMaintenanceMapper deviceMaintenanceMapper;
@@ -38,43 +37,42 @@
    private SparePartsMapper sparePartsMapper;
    @Autowired
    private SparePartsRequisitionRecordService sparePartsRequisitionRecordService;
    @Autowired
    private IDeviceLedgerService deviceLedgerService;
    @Override
    public IPage<DeviceMaintenanceDto> queryPage(Page page, DeviceMaintenanceDto deviceMaintenanceDto) {
        return deviceMaintenanceMapper.queryPage(page, deviceMaintenanceDto);
    }
    @Override
    public AjaxResult saveDeviceRepair(DeviceMaintenance deviceMaintenance) {
        boolean save = this.save(deviceMaintenance);
        if (save){
            return AjaxResult.success();
        List<DeviceMaintenance> records = buildMaintenanceRecords(deviceMaintenance);
        if (records.isEmpty()) {
            return AjaxResult.error("请选择设备");
        }
        return AjaxResult.error();
        return this.saveBatch(records) ? AjaxResult.success() : AjaxResult.error();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult updateDeviceDeviceMaintenance(DeviceMaintenance deviceMaintenance) {
        DeviceMaintenance oldDeviceMaintenance = this.getById(deviceMaintenance.getId());
        // å¤„理备件使用情况
        if (oldDeviceMaintenance == null) {
            return AjaxResult.error("保养记录不存在");
        }
        if (com.github.xiaoymin.knife4j.core.util.CollectionUtils.isNotEmpty(deviceMaintenance.getSparePartsUseList())) {
            List<Long> sparePartIds = new ArrayList<>();
            for (DeviceMaintenance.SparePartUse sparePartUse : deviceMaintenance.getSparePartsUseList()) {
                // èŽ·å–å¤‡ä»¶ä¿¡æ¯
                SpareParts spareParts = sparePartsMapper.selectById(sparePartUse.getId());
                if (spareParts != null) {
                    // æ£€æŸ¥æ•°é‡æ˜¯å¦è¶³å¤Ÿ
                    if (spareParts.getQuantity().compareTo(new BigDecimal(sparePartUse.getQuantity())) >= 0) {
                        // æ›´æ–°æ•°é‡
                        spareParts.setQuantity(spareParts.getQuantity().subtract(new BigDecimal(sparePartUse.getQuantity())));
                        sparePartsMapper.updateById(spareParts);
                        sparePartIds.add(sparePartUse.getId());
                        // åˆ›å»ºå¤‡ä»¶é¢†ç”¨è®°å½•
                        SparePartsRequisitionRecord record = new SparePartsRequisitionRecord();
                        record.setSourceType(1); // 1 ä¿å…»
                        record.setSourceType(1);
                        record.setSourceId(deviceMaintenance.getId());
                        record.setDeviceLedgerId(oldDeviceMaintenance.getDeviceLedgerId());
                        record.setSparePartsId(sparePartUse.getId());
@@ -85,36 +83,64 @@
                    }
                }
            }
            // æ›´æ–°å¤‡ä»¶IDs字段
            if (!sparePartIds.isEmpty()) {
                deviceMaintenance.setSparePartsIds(StringUtils.join(sparePartIds, ","));
            }
        }
        if (this.updateById(deviceMaintenance)) {
            return AjaxResult.success();
        }
        return AjaxResult.error();
        return this.updateById(deviceMaintenance) ? AjaxResult.success() : AjaxResult.error();
    }
    @Override
    public void export(HttpServletResponse response, Long[] ids) {
        List<DeviceMaintenance> supplierManageList = deviceMaintenanceMapper.selectList(null);
        ArrayList<DeviceMaintenanceExeclDto> deviceLedgerExeclDtos = new ArrayList<>();
        ArrayList<DeviceMaintenanceExeclDto> result = new ArrayList<>();
        supplierManageList.forEach(deviceMaintenance -> {
            DeviceMaintenanceExeclDto deviceRepairExeclDto = new DeviceMaintenanceExeclDto();
            BeanUtils.copyProperties(deviceMaintenance,deviceRepairExeclDto);
            deviceRepairExeclDto.setStatus(deviceMaintenance.getStatus() == 0 ? "待维修" : deviceMaintenance.getStatus() == 1 ? "完结" : "失败");
//            deviceRepairExeclDto.setMaintenanceResult(deviceMaintenance.getMaintenanceResult() != null && deviceMaintenance.getMaintenanceResult() == 0 ? "ç»´ä¿®" : "完好");
            deviceLedgerExeclDtos.add(deviceRepairExeclDto);
            DeviceMaintenanceExeclDto dto = new DeviceMaintenanceExeclDto();
            BeanUtils.copyProperties(deviceMaintenance, dto);
            dto.setStatus(deviceMaintenance.getStatus() == 0 ? "待维修" : deviceMaintenance.getStatus() == 1 ? "完结" : "失败");
            result.add(dto);
        });
        ExcelUtil<DeviceMaintenanceExeclDto> util = new ExcelUtil<DeviceMaintenanceExeclDto>(DeviceMaintenanceExeclDto.class);
        util.exportExcel(response, deviceLedgerExeclDtos, "设备报修导出");
        ExcelUtil<DeviceMaintenanceExeclDto> util = new ExcelUtil<>(DeviceMaintenanceExeclDto.class);
        util.exportExcel(response, result, "设备保养导出");
    }
    @Override
    public DeviceMaintenanceDto detailById(Long id) {
        return deviceMaintenanceMapper.detailById(id);
    }
    private List<DeviceMaintenance> buildMaintenanceRecords(DeviceMaintenance source) {
        Long[] deviceIds = source.getDeviceLedgerIds();
        if (deviceIds == null || deviceIds.length == 0) {
            deviceIds = source.getDeviceLedgerId() == null ? new Long[0] : new Long[]{source.getDeviceLedgerId()};
        }
        List<DeviceMaintenance> records = new ArrayList<>();
        Arrays.stream(deviceIds).distinct().forEach(deviceId -> {
            DeviceLedger deviceLedger = deviceLedgerService.getById(deviceId);
            if (deviceLedger == null) {
                return;
            }
            int quantity = resolveQuantity(deviceLedger);
            for (int i = 0; i < quantity; i++) {
                DeviceMaintenance record = new DeviceMaintenance();
                BeanUtils.copyProperties(source, record);
                record.setId(null);
                record.setDeviceLedgerIds(null);
                record.setDeviceLedgerId(deviceLedger.getId());
                record.setAreaId(deviceLedger.getAreaId());
                record.setDeviceName(deviceLedger.getDeviceName());
                record.setDeviceModel(deviceLedger.getDeviceModel());
                records.add(record);
            }
        });
        return records;
    }
    private int resolveQuantity(DeviceLedger deviceLedger) {
        if (deviceLedger == null || deviceLedger.getNumber() == null || deviceLedger.getNumber().compareTo(BigDecimal.ONE) < 0) {
            return 1;
        }
        return Math.max(1, deviceLedger.getNumber().intValue());
    }
}
src/main/java/com/ruoyi/device/service/impl/DeviceRepairServiceImpl.java
@@ -20,7 +20,6 @@
import com.ruoyi.measuringinstrumentledger.pojo.SpareParts;
import com.ruoyi.measuringinstrumentledger.pojo.SparePartsRequisitionRecord;
import com.ruoyi.measuringinstrumentledger.service.SparePartsRequisitionRecordService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
@@ -34,7 +33,6 @@
import java.util.List;
@Service
@AllArgsConstructor
@Slf4j
public class DeviceRepairServiceImpl extends ServiceImpl<DeviceRepairMapper, DeviceRepair> implements IDeviceRepairService {
@@ -51,43 +49,37 @@
    @Override
    public IPage<DeviceRepairDto> queryPage(Page page, DeviceRepairDto deviceRepairDto) {
        return deviceRepairMapper.queryPage(page, deviceRepairDto);
    }
    @Override
    public AjaxResult saveDeviceRepair(DeviceRepair deviceRepair) {
        DeviceLedger byId = deviceLedgerService.getById(deviceRepair.getDeviceLedgerId());
        deviceRepair.setDeviceName(byId.getDeviceName());
        deviceRepair.setDeviceModel(byId.getDeviceModel());
        boolean save = this.save(deviceRepair);
        if (save){
            return AjaxResult.success();
        List<DeviceRepair> records = buildRepairRecords(deviceRepair);
        if (records.isEmpty()) {
            return AjaxResult.error("请选择设备");
        }
        return AjaxResult.error();
        return this.saveBatch(records) ? AjaxResult.success() : AjaxResult.error();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult updateDeviceRepair(DeviceRepair deviceRepair) {
        DeviceRepair oldDeviceRepair = this.getById(deviceRepair.getId());
        // å¤„理备件使用情况
        if (oldDeviceRepair == null) {
            return AjaxResult.error("报修记录不存在");
        }
        if (CollectionUtils.isNotEmpty(deviceRepair.getSparePartsUseList())) {
            List<Long> sparePartIds = new ArrayList<>();
            for (DeviceRepair.SparePartUse sparePartUse : deviceRepair.getSparePartsUseList()) {
                // èŽ·å–å¤‡ä»¶ä¿¡æ¯
                SpareParts spareParts = sparePartsMapper.selectById(sparePartUse.getId());
                if (spareParts != null) {
                    // æ£€æŸ¥æ•°é‡æ˜¯å¦è¶³å¤Ÿ
                    if (spareParts.getQuantity().compareTo(new BigDecimal(sparePartUse.getQuantity())) >= 0) {
                        // æ›´æ–°æ•°é‡
                        spareParts.setQuantity(spareParts.getQuantity().subtract(new BigDecimal(sparePartUse.getQuantity())));
                        sparePartsMapper.updateById(spareParts);
                        sparePartIds.add(sparePartUse.getId());
                        // åˆ›å»ºå¤‡ä»¶é¢†ç”¨è®°å½•
                        SparePartsRequisitionRecord record = new SparePartsRequisitionRecord();
                        record.setSourceType(0); // 0 ç»´ä¿®
                        record.setSourceType(0);
                        record.setSourceId(deviceRepair.getId());
                        record.setDeviceLedgerId(oldDeviceRepair.getDeviceLedgerId());
                        record.setSparePartsId(sparePartUse.getId());
@@ -98,7 +90,6 @@
                    }
                }
            }
            // æ›´æ–°å¤‡ä»¶IDs字段
            if (!sparePartIds.isEmpty()) {
                deviceRepair.setSparePartsIds(StringUtils.join(sparePartIds, ","));
            }
@@ -106,7 +97,6 @@
        if (this.updateById(deviceRepair)) {
            Long id = deviceRepair.getId();
            //
            DeviceDefectRecordDto deviceDefectRecordDto = new DeviceDefectRecordDto();
            deviceDefectRecordDto.setDeviceLedgerId(id);
            deviceDefectRecordDto.setStatus("严重缺陷");
@@ -124,42 +114,59 @@
    @Override
    public void export(HttpServletResponse response, Long[] ids) {
        List<DeviceRepair> supplierManageList;
        if (ids == null || ids.length == 0) {
            List<DeviceRepair> supplierManageList = this.list();
            ArrayList<DeviceRepairExeclDto> deviceLedgerExeclDtos = new ArrayList<>();
            supplierManageList.stream().forEach(deviceRepair -> {
                DeviceRepairExeclDto deviceRepairExeclDto = new DeviceRepairExeclDto();
                BeanUtils.copyProperties(deviceRepair,deviceRepairExeclDto);
                deviceRepairExeclDto.setStatusStr(deviceRepair.getStatus() == 0 ? "待维修" : deviceRepair.getStatus() == 1 ? "完结" : "失败");
                deviceLedgerExeclDtos.add(deviceRepairExeclDto);
            });
            ExcelUtil<DeviceRepairExeclDto> util = new ExcelUtil<DeviceRepairExeclDto>(DeviceRepairExeclDto.class);
            util.exportExcel(response, deviceLedgerExeclDtos, "设备报修导出");
            supplierManageList = this.list();
        }else {
            ArrayList<Long> arrayList = new ArrayList<>();
            Arrays.stream(ids).map(id -> {
                return arrayList.add( id);
            });
            List<DeviceRepair> supplierManageList = deviceRepairMapper.selectBatchIds(arrayList);
            ArrayList<DeviceRepairExeclDto> deviceLedgerExeclDtos = new ArrayList<>();
            supplierManageList.stream().forEach(deviceRepair -> {
                DeviceRepairExeclDto deviceRepairExeclDto = new DeviceRepairExeclDto();
                BeanUtils.copyProperties(deviceRepair,deviceRepairExeclDto);
                deviceRepairExeclDto.setStatusStr(deviceRepair.getStatus() == 0 ? "待维修" : deviceRepair.getStatus() == 1 ? "完结" : "失败");
                deviceLedgerExeclDtos.add(deviceRepairExeclDto);
            });
            ExcelUtil<DeviceRepairExeclDto> util = new ExcelUtil<DeviceRepairExeclDto>(DeviceRepairExeclDto.class);
            util.exportExcel(response, deviceLedgerExeclDtos, "设备报修导出");
            supplierManageList = deviceRepairMapper.selectBatchIds(Arrays.asList(ids));
        }
        ArrayList<DeviceRepairExeclDto> result = new ArrayList<>();
        supplierManageList.forEach(deviceRepair -> {
            DeviceRepairExeclDto dto = new DeviceRepairExeclDto();
            BeanUtils.copyProperties(deviceRepair, dto);
            dto.setStatusStr(deviceRepair.getStatus() == 0 ? "待维修" : deviceRepair.getStatus() == 1 ? "完结" : "失败");
            result.add(dto);
        });
        ExcelUtil<DeviceRepairExeclDto> util = new ExcelUtil<>(DeviceRepairExeclDto.class);
        util.exportExcel(response, result, "设备报修导出");
    }
    @Override
    public DeviceRepairDto detailById(Long id) {
        return deviceRepairMapper.detailById(id);
    }
    private List<DeviceRepair> buildRepairRecords(DeviceRepair source) {
        Long[] deviceIds = source.getDeviceLedgerIds();
        if (deviceIds == null || deviceIds.length == 0) {
            deviceIds = source.getDeviceLedgerId() == null ? new Long[0] : new Long[]{source.getDeviceLedgerId()};
        }
        List<DeviceRepair> records = new ArrayList<>();
        Arrays.stream(deviceIds).distinct().forEach(deviceId -> {
            DeviceLedger deviceLedger = deviceLedgerService.getById(deviceId);
            if (deviceLedger == null) {
                return;
            }
            int quantity = resolveQuantity(deviceLedger);
            for (int i = 0; i < quantity; i++) {
                DeviceRepair record = new DeviceRepair();
                BeanUtils.copyProperties(source, record);
                record.setId(null);
                record.setDeviceLedgerIds(null);
                record.setDeviceLedgerId(deviceLedger.getId());
                record.setAreaId(deviceLedger.getAreaId());
                record.setDeviceName(deviceLedger.getDeviceName());
                record.setDeviceModel(deviceLedger.getDeviceModel());
                records.add(record);
            }
        });
        return records;
    }
    private int resolveQuantity(DeviceLedger deviceLedger) {
        if (deviceLedger == null || deviceLedger.getNumber() == null || deviceLedger.getNumber().compareTo(BigDecimal.ONE) < 0) {
            return 1;
        }
        return Math.max(1, deviceLedger.getNumber().intValue());
    }
}
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskJob.java
@@ -1,8 +1,14 @@
package com.ruoyi.device.service.impl;
import com.ruoyi.device.mapper.DeviceLedgerMapper;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.device.pojo.DeviceMaintenance;
import com.ruoyi.device.pojo.MaintenanceTask;
import org.quartz.*;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
@@ -13,14 +19,16 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Component
@DisallowConcurrentExecution // ç¦æ­¢å¹¶å‘执行同一个Job
@DisallowConcurrentExecution
public class MaintenanceTaskJob implements Job, Serializable {
    private static final long serialVersionUID = 1L; // å¿…须定义序列化ID
    private static final long serialVersionUID = 1L;
    @Autowired
    private DeviceMaintenanceServiceImpl deviceMaintenanceService;
@@ -28,15 +36,15 @@
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private DeviceLedgerMapper deviceLedgerMapper;
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        // ä¿®å¤ç±»åž‹è½¬æ¢é”™è¯¯ï¼Œæ­£ç¡®èŽ·å–taskId
        Long taskId = jobDataMap.getLong("maintenanceTaskId");
        try {
            // 3. å°è¯•查询你的业务数据
            // é€šè¿‡JDBC模板查询定时任务信息,使用参数化查询防止SQL注入
            String yourSql = "SELECT * FROM maintenance_task where id = ?";
            List<MaintenanceTask> tasks = jdbcTemplate.query(
                    yourSql,
@@ -45,58 +53,49 @@
            );
            MaintenanceTask timingTask = tasks.isEmpty() ? null : tasks.get(0);
            if (timingTask == null) {
                throw new JobExecutionException("MaintenanceTaskJob找不到定时任务: " + taskId);
                throw new JobExecutionException("MaintenanceTaskJob找不到定时任务 " + taskId);
            }
            // 2. åˆ›å»ºå¹¶ä¿å­˜å·¡æ£€ä»»åŠ¡è®°å½• - è¿™å°±æ˜¯æ‚¨æä¾›çš„代码应该放的位置
            DeviceMaintenance deviceMaintenance = createInspectionTask(timingTask);
            deviceMaintenanceService.save(deviceMaintenance);
            List<Long> deviceIds = resolveDeviceIds(timingTask);
            List<DeviceMaintenance> maintenanceList = new ArrayList<>();
            for (Long deviceId : deviceIds) {
                int quantity = resolveQuantity(deviceId);
                for (int i = 0; i < quantity; i++) {
                    maintenanceList.add(createInspectionTask(timingTask, deviceId));
                }
            }
            deviceMaintenanceService.saveBatch(maintenanceList);
            // 3. æ›´æ–°å®šæ—¶ä»»åŠ¡çš„æ‰§è¡Œæ—¶é—´
            if (!tasks.isEmpty()) {
                MaintenanceTask task = tasks.get(0);
                // æ›´æ–°æœ€åŽæ‰§è¡Œæ—¶é—´ä¸ºå½“前时间
                LocalDateTime lastExecutionTime = LocalDateTime.now();
                // è®¡ç®—下次执行时间
                LocalDateTime nextExecutionTime = calculateNextExecutionTime(
                        task.getFrequencyType(),
                        task.getFrequencyDetail(),
                        lastExecutionTime
                );
                // æ‰§è¡Œæ›´æ–°æ“ä½œ
                String updateSql = "UPDATE maintenance_task " +
                        "SET last_execution_time = ?, next_execution_time = ? " +
                        "WHERE id = ?";
                jdbcTemplate.update(
                        updateSql,
                        lastExecutionTime,
                        nextExecutionTime,
                        taskId
                );
                String updateSql = "UPDATE maintenance_task SET last_execution_time = ?, next_execution_time = ? WHERE id = ?";
                jdbcTemplate.update(updateSql, lastExecutionTime, nextExecutionTime, taskId);
            }
        } catch (Exception e) {
            throw new JobExecutionException(e);
        }
    }
    // è¿™å°±æ˜¯æ‚¨æä¾›çš„代码封装成的方法
    private DeviceMaintenance createInspectionTask(MaintenanceTask timingTask) {
    private DeviceMaintenance createInspectionTask(MaintenanceTask timingTask, Long deviceLedgerId) {
        DeviceMaintenance inspectionTask = new DeviceMaintenance();
        // å¤åˆ¶åŸºæœ¬å±žæ€§
        inspectionTask.setDeviceName(timingTask.getTaskName());
        DeviceLedger deviceLedger = deviceLedgerMapper.selectById(deviceLedgerId);
        inspectionTask.setDeviceName(deviceLedger != null ? deviceLedger.getDeviceName() : timingTask.getTaskName());
        inspectionTask.setMaintenanceTaskId(timingTask.getId());
        inspectionTask.setDeviceLedgerId(timingTask.getTaskId());
        inspectionTask.setDeviceLedgerId(deviceLedgerId);
        inspectionTask.setAreaId(timingTask.getAreaId() != null ? timingTask.getAreaId() : (deviceLedger != null ? deviceLedger.getAreaId() : null));
        inspectionTask.setMaintenancePlanTime(LocalDateTime.now());
        inspectionTask.setFrequencyType(timingTask.getFrequencyType());
        inspectionTask.setFrequencyDetail(timingTask.getFrequencyDetail());
        inspectionTask.setTenantId(timingTask.getTenantId());
        inspectionTask.setStatus(0);
        inspectionTask.setDeviceModel(timingTask.getDeviceModel());
        inspectionTask.setDeviceModel(deviceLedger != null ? deviceLedger.getDeviceModel() : timingTask.getDeviceModel());
        inspectionTask.setCreateUser(Integer.parseInt(timingTask.getRegistrantId().toString()));
        inspectionTask.setUpdateTime(LocalDateTime.now());
        inspectionTask.setCreateTime(LocalDateTime.now());
@@ -104,13 +103,33 @@
        return inspectionTask;
    }
    private List<Long> resolveDeviceIds(MaintenanceTask timingTask) {
        if (timingTask.getDeviceLedgerIdsStr() != null && !timingTask.getDeviceLedgerIdsStr().trim().isEmpty()) {
            return Arrays.stream(timingTask.getDeviceLedgerIdsStr().split(","))
                    .map(String::trim)
                    .filter(item -> !item.isEmpty())
                    .map(Long::valueOf)
                    .distinct()
                    .collect(java.util.stream.Collectors.toList());
        }
        List<Long> ids = new ArrayList<>();
        if (timingTask.getTaskId() != null) {
            ids.add(timingTask.getTaskId());
        }
        return ids;
    }
    /**
     * è®¡ç®—下次执行时间
     */
    private LocalDateTime calculateNextExecutionTime(String frequencyType,
                                                     String frequencyDetail,
                                                     LocalDateTime currentTime) {
    private int resolveQuantity(Long deviceLedgerId) {
        try {
            String sql = "SELECT number FROM device_ledger WHERE id = ?";
            Number quantity = jdbcTemplate.queryForObject(sql, Number.class, deviceLedgerId);
            return quantity == null || quantity.intValue() < 1 ? 1 : quantity.intValue();
        } catch (Exception ignored) {
            return 1;
        }
    }
    private LocalDateTime calculateNextExecutionTime(String frequencyType, String frequencyDetail, LocalDateTime currentTime) {
        try {
            switch (frequencyType) {
                case "DAILY":
@@ -129,84 +148,52 @@
        }
    }
    /**
     * è®¡ç®—每日任务的下次执行时间
     */
    private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime current) {
        LocalTime executionTime = LocalTime.parse(timeStr); // è§£æžæ ¼å¼ "HH:mm"
        LocalTime executionTime = LocalTime.parse(timeStr);
        LocalDateTime nextTime = LocalDateTime.of(current.toLocalDate(), executionTime);
        // å¦‚果今天的时间已过,则安排明天
        return current.isBefore(nextTime) ? nextTime : nextTime.plusDays(1);
    }
    /**
     * è®¡ç®—每周任务的下次执行时间
     */
    private LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        String dayOfWeekStr = parts[0];  // å¦‚ "MON" æˆ– "MON|WED|FRI"
        LocalTime time = LocalTime.parse(parts[1]); // æ—¶é—´éƒ¨åˆ†
        // è§£æžæ˜ŸæœŸå‡ (支持多个星期)
        String dayOfWeekStr = parts[0];
        LocalTime time = LocalTime.parse(parts[1]);
        Set<DayOfWeek> targetDays = parseDayOfWeeks(dayOfWeekStr);
        // ä»Žå½“前时间开始找下一个符合条件的星期几
        LocalDateTime nextTime = current;
        while (true) {
            nextTime = nextTime.plusDays(1);
            if (targetDays.contains(nextTime.getDayOfWeek())) {
                return LocalDateTime.of(nextTime.toLocalDate(), time);
            }
            // é˜²æ­¢æ— é™å¾ªçޝ(理论上不会发生)
            if (nextTime.isAfter(current.plusYears(1))) {
                throw new RuntimeException("无法找到下次执行时间");
            }
        }
    }
    /**
     * è®¡ç®—每月任务的下次执行时间
     */
    private LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int dayOfMonth = Integer.parseInt(parts[0]);
        LocalTime time = LocalTime.parse(parts[1]);
        // ä»Žä¸‹ä¸ªæœˆå¼€å§‹è®¡ç®—
        LocalDateTime nextTime = current.plusMonths(1)
        return current.plusMonths(1)
                .withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).toLocalDate().lengthOfMonth()))
                .with(time);
        return nextTime;
    }
    /**
     * è®¡ç®—每季度任务的下次执行时间
     */
    private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int quarterMonth = Integer.parseInt(parts[0]); // 1=第1个月,2=第2个月,3=第3个月
        int quarterMonth = Integer.parseInt(parts[0]);
        int dayOfMonth = Integer.parseInt(parts[1]);
        LocalTime time = LocalTime.parse(parts[2]);
        // è®¡ç®—当前季度
        int currentQuarter = (current.getMonthValue() - 1) / 3 + 1;
        int currentMonthInQuarter = (current.getMonthValue() - 1) % 3 + 1;
        YearMonth targetYearMonth;
        if (currentMonthInQuarter < quarterMonth) {
            // æœ¬å­£åº¦å†…还有执行机会
            targetYearMonth = YearMonth.from(current)
                    .plusMonths(quarterMonth - currentMonthInQuarter);
            targetYearMonth = YearMonth.from(current).plusMonths(quarterMonth - currentMonthInQuarter);
        } else {
            // éœ€è¦åˆ°ä¸‹ä¸ªå­£åº¦
            targetYearMonth = YearMonth.from(current)
                    .plusMonths(3 - currentMonthInQuarter + quarterMonth);
            targetYearMonth = YearMonth.from(current).plusMonths(3 - currentMonthInQuarter + quarterMonth);
        }
        // å¤„理月末日期
        int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
        return LocalDateTime.of(
@@ -218,26 +205,36 @@
        );
    }
    /**
     * è§£æžæ˜ŸæœŸå‡ å­—符串
     */
    private Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) {
        Set<DayOfWeek> days = new HashSet<>();
        String[] dayStrs = dayOfWeekStr.split("\\|");
        for (String dayStr : dayStrs) {
            switch (dayStr) {
                case "MON": days.add(DayOfWeek.MONDAY); break;
                case "TUE": days.add(DayOfWeek.TUESDAY); break;
                case "WED": days.add(DayOfWeek.WEDNESDAY); break;
                case "THU": days.add(DayOfWeek.THURSDAY); break;
                case "FRI": days.add(DayOfWeek.FRIDAY); break;
                case "SAT": days.add(DayOfWeek.SATURDAY); break;
                case "SUN": days.add(DayOfWeek.SUNDAY); break;
                default: throw new IllegalArgumentException("无效的星期几: " + dayStr);
                case "MON":
                    days.add(DayOfWeek.MONDAY);
                    break;
                case "TUE":
                    days.add(DayOfWeek.TUESDAY);
                    break;
                case "WED":
                    days.add(DayOfWeek.WEDNESDAY);
                    break;
                case "THU":
                    days.add(DayOfWeek.THURSDAY);
                    break;
                case "FRI":
                    days.add(DayOfWeek.FRIDAY);
                    break;
                case "SAT":
                    days.add(DayOfWeek.SATURDAY);
                    break;
                case "SUN":
                    days.add(DayOfWeek.SUNDAY);
                    break;
                default:
                    throw new IllegalArgumentException("无效的星期几: " + dayStr);
            }
        }
        return days;
    }
}
src/main/java/com/ruoyi/device/service/impl/MaintenanceTaskServiceImpl.java
@@ -2,9 +2,14 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.device.mapper.DeviceAreaMapper;
import com.ruoyi.device.mapper.MaintenanceTaskMapper;
import com.ruoyi.device.pojo.DeviceArea;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.device.pojo.MaintenanceTask;
import com.ruoyi.device.service.IDeviceLedgerService;
import com.ruoyi.device.service.MaintenanceTaskService;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.inspectiontask.pojo.TimingTask;
@@ -16,65 +21,81 @@
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
 * @author :yys
 * @date : 2025/12/22 14:57
 */
@Service
@Slf4j
public class MaintenanceTaskServiceImpl extends ServiceImpl<MaintenanceTaskMapper, MaintenanceTask> implements MaintenanceTaskService {
    @Autowired
    private MaintenanceTaskMapper maintenanceTaskMapper;
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private TimingTaskServiceImpl timingTaskService;
    @Autowired
    private MaintenanceTaskScheduler maintenanceTaskScheduler;
    @Autowired
    private IDeviceLedgerService deviceLedgerService;
    @Autowired
    private DeviceAreaMapper deviceAreaMapper;
    @Override
    public AjaxResult listPage(Page page, MaintenanceTask maintenanceTask) {
        Page<MaintenanceTask> taskPage = maintenanceTaskMapper.selectPage(page, null);
        // 2. å¦‚果没有数据,直接返回空分页
        com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MaintenanceTask> queryWrapper =
                new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>();
        if (StringUtils.isNotEmpty(maintenanceTask.getTaskName())) {
            queryWrapper.like(MaintenanceTask::getTaskName, maintenanceTask.getTaskName());
        }
        if (maintenanceTask.getAreaId() != null) {
            queryWrapper.eq(MaintenanceTask::getAreaId, maintenanceTask.getAreaId());
        }
        Page<MaintenanceTask> taskPage = maintenanceTaskMapper.selectPage(page, queryWrapper);
        if (taskPage.getRecords().isEmpty()) {
            return AjaxResult.success(taskPage);
        }
        // 3. æ”¶é›†æ‰€æœ‰éœ€è¦æŸ¥è¯¢çš„用户ID
        Set<Long> userIds = new HashSet<>();
        // æ”¶é›†ç™»è®°äººID
        taskPage.getRecords().forEach(task -> {
            if (task.getRegistrantId() != null) {
                userIds.add(task.getRegistrantId());
            }
        });
        // 4. æ‰¹é‡æŸ¥è¯¢ç”¨æˆ·ä¿¡æ¯
        Map<Long, String> userNickNameMap = new HashMap<>();
        if (!userIds.isEmpty()) {
            List<SysUser> users = sysUserMapper.selectUserByIds((new ArrayList<>(userIds)));
            List<SysUser> users = sysUserMapper.selectUserByIds(new ArrayList<>(userIds));
            users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName()));
        }
        Map<Long, String> areaNameMap = deviceAreaMapper.selectBatchIds(taskPage.getRecords().stream()
                        .map(MaintenanceTask::getAreaId)
                        .filter(java.util.Objects::nonNull)
                        .distinct()
                        .collect(Collectors.toList()))
                .stream()
                .collect(Collectors.toMap(DeviceArea::getId, DeviceArea::getAreaName, (left, right) -> left, HashMap::new));
        taskPage.getRecords().forEach(task -> {
            // è®¾ç½®ç™»è®°äººæ˜µç§°
            if (task.getRegistrantId() != null) {
                task.setRegistrant(userNickNameMap.getOrDefault(task.getRegistrantId(), "未知用户"));
            }
            task.setAreaName(areaNameMap.get(task.getAreaId()));
        });
        return AjaxResult.success(taskPage);
    }
    @Override
    public AjaxResult add(MaintenanceTask maintenanceTask) {
        if (!prepareMaintenanceTask(maintenanceTask)) {
            return AjaxResult.error("请选择设备");
        }
        maintenanceTask.setActive(true);
        // è®¡ç®—首次执行时间
        TimingTask task = new TimingTask();
        task.setFrequencyType(maintenanceTask.getFrequencyType());
        task.setFrequencyDetail(maintenanceTask.getFrequencyDetail());
@@ -94,6 +115,10 @@
            return AjaxResult.warn("没有此数据");
        }
        BeanUtils.copyProperties(maintenanceTask, maintenanceTask1);
        if (!prepareMaintenanceTask(maintenanceTask1)) {
            return AjaxResult.error("请选择设备");
        }
        maintenanceTask1.setDeviceLedgerIds(null);
        int update = maintenanceTaskMapper.updateById(maintenanceTask1);
        if (update > 0) {
            maintenanceTaskScheduler.rescheduleMaintenanceTask(maintenanceTask1);
@@ -105,10 +130,40 @@
    public AjaxResult delete(List<Long> ids) {
        int delete = maintenanceTaskMapper.deleteBatchIds(ids);
        if (delete > 0) {
            ids.forEach(id -> {
                maintenanceTaskScheduler.unscheduleMaintenanceTask(id);
            });
            ids.forEach(id -> maintenanceTaskScheduler.unscheduleMaintenanceTask(id));
        }
        return AjaxResult.success("删除成功");
    }
    private boolean prepareMaintenanceTask(MaintenanceTask task) {
        Long[] deviceIds = task.getDeviceLedgerIds();
        if ((deviceIds == null || deviceIds.length == 0) && StringUtils.isNotEmpty(task.getDeviceLedgerIdsStr())) {
            deviceIds = Arrays.stream(task.getDeviceLedgerIdsStr().split(","))
                    .filter(StringUtils::isNotEmpty)
                    .map(Long::valueOf)
                    .toArray(Long[]::new);
        }
        if (deviceIds == null || deviceIds.length == 0) {
            deviceIds = task.getTaskId() == null ? new Long[0] : new Long[]{task.getTaskId()};
        }
        List<Long> validIds = Arrays.stream(deviceIds).distinct().collect(Collectors.toList());
        if (validIds.isEmpty()) {
            return false;
        }
        List<DeviceLedger> devices = validIds.stream()
                .map(deviceLedgerService::getById)
                .filter(device -> device != null)
                .collect(Collectors.toList());
        if (devices.isEmpty()) {
            return false;
        }
        task.setTaskId(devices.get(0).getId());
        if (task.getAreaId() == null) {
            task.setAreaId(devices.get(0).getAreaId());
        }
        task.setDeviceLedgerIdsStr(devices.stream().map(item -> String.valueOf(item.getId())).collect(Collectors.joining(",")));
        task.setTaskName(devices.stream().map(DeviceLedger::getDeviceName).filter(StringUtils::isNotEmpty).distinct().collect(Collectors.joining(",")));
        task.setDeviceModel(devices.stream().map(DeviceLedger::getDeviceModel).filter(StringUtils::isNotEmpty).distinct().collect(Collectors.joining(",")));
        return true;
    }
}
src/main/java/com/ruoyi/http/service/controller/JclyController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package com.ruoyi.http.service.controller;
import com.ruoyi.framework.web.controller.BaseController;
import com.ruoyi.framework.web.domain.AjaxResult;
import com.ruoyi.http.service.impl.RealTimeEnergyConsumptionServiceImpl;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.naming.ldap.PagedResultsControl;
import java.util.*;
@RestController
@RequestMapping("/iot")
@Api(tags = "数采接口")
public class JclyController extends BaseController {
    @Autowired
    private RealTimeEnergyConsumptionServiceImpl realTimeEnergyConsumptionService;
    private final String DEVICE_GUID = "90444196515214284663";
    private final String DEVICE_GUID_2 = "90187099059463151919";
    private final String DEVICE_GUID_3 = "90299548548536240693";
    private final String DEVICE_GUID_4 = "90558670647417764794";
    private final String DEVICE_GUID_5 = "90802100373808917949";
    private final String DEVICE_GUID_6 = "90878497978270816672";
    /**
     * å®žæ—¶èŽ·å–æ¸©æ¹¿åº¦ï¼ŒäºŒæ°§åŒ–ç¢³æ•°æ®
     */
    @GetMapping("/getRealData")
    public AjaxResult getRealData() {
        List<Map<String,String>> maps = realTimeEnergyConsumptionService
                .getRealData(Arrays.
                        asList(DEVICE_GUID,
                                DEVICE_GUID_2,
                                DEVICE_GUID_3,
                                DEVICE_GUID_4,
                                DEVICE_GUID_5,
                                DEVICE_GUID_6));
        return AjaxResult.success(maps);
    }
}
src/main/java/com/ruoyi/http/service/impl/RealTimeEnergyConsumptionServiceImpl.java
@@ -6,9 +6,11 @@
import com.ruoyi.common.utils.http.HttpUtils;
import com.ruoyi.http.service.RealTimeEnergyConsumptionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -25,7 +27,7 @@
    private static final String TOKEN_URL = "/elitechAccess/getToken";
    private static final String REAL_TIME_URL = "/elitechAccess/v2/getRealTimeData"; //获取设备实时数据
    private static final String REAL_TIME_URL = "/elitechAccess/v2/getRealTimeData";
    private static final String KET_ID = "75804708";
@@ -35,66 +37,87 @@
    private static final String PASS_WORD = "y17775163675";
    private static final String DEVICE_GUID = "90444196515214284663";
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    /**
     * æ ¹æ®paramCode提取探头参数
     * @param paramList è®¾å¤‡å‚数数组
     * @param targetCode ç›®æ ‡æŽ¢å¤´ç¼–码
     * @return æŽ¢å¤´å‚数对象(包含name/value/unit)
     * æ ¹æ® paramCode æå–探头参数。
     */
    private static JSONObject getProbeParam(JSONArray paramList, String targetCode) {
        if (paramList == null) {
            return new JSONObject();
        }
        for (int i = 0; i < paramList.size(); i++) {
            JSONObject paramObj = paramList.getJSONObject(i);
            if (targetCode.equals(paramObj.getString("paramCode"))) {
                return paramObj;
            }
        }
        return new JSONObject(); // æœªåŒ¹é…åˆ°è¿”回空对象,避免空指针
        return new JSONObject();
    }
    /**
     * å®žæ—¶èŽ·å–æ¸©æ¹¿åº¦ï¼ŒäºŒæ°§åŒ–ç¢³æ•°æ®
     * å®žæ—¶èŽ·å–æ¸©æ¹¿åº¦ã€å…‰ç…§ã€äºŒæ°§åŒ–ç¢³æ•°æ®ã€‚
     */
    public static void main(String[] args) {
        String realTimeData = getRealTimeData(getToken());
    public List<Map<String, String>> getRealData(List<String> guidList) {
        log.info("开始获取实时数据");
        List<Map<String, String>> listMaps = new ArrayList<>();
        String realTimeData = getRealTimeData(getToken(), guidList);
        Map<String, Object> map = JSON.parseObject(realTimeData, Map.class);
        if(map.get("code").equals(0)){
            // 1. è§£æžå¤–层data为JSON数组(接口返回的设备列表)
            JSONArray deviceList = JSON.parseArray(map.get("data").toString());
            // 2. éåŽ†è®¾å¤‡åˆ—è¡¨ï¼ˆæ­¤å¤„ä»…å–ç¬¬ä¸€ä¸ªè®¾å¤‡ï¼Œè‹¥æœ‰å¤šä¸ªè®¾å¤‡å¯å¾ªçŽ¯å¤„ç†ï¼‰
            if (!deviceList.isEmpty()) {
                JSONObject deviceObj = deviceList.getJSONObject(0);
                // 3. è§£æžè®¾å¤‡å†…的参数数组(所有paramCode对应的参数)
                JSONArray paramList = deviceObj.getJSONArray("data");
        if (!Integer.valueOf(0).equals(map.get("code"))) {
            return listMaps;
        }
                // 4. å®šä¹‰ç›®æ ‡æŽ¢å¤´çš„paramCode,按需扩展
        JSONArray deviceList = JSON.parseArray(String.valueOf(map.get("data")));
        if (deviceList == null || deviceList.isEmpty()) {
            return listMaps;
        }
                String[] targetCodes = {"0100", "0110", "0120", "0130"};
        for (int deviceIndex = 0; deviceIndex < deviceList.size(); deviceIndex++) {
            JSONObject deviceObj = deviceList.getJSONObject(deviceIndex);
            JSONArray paramList = deviceObj.getJSONArray("data");
            Map<String, String> deviceData = new HashMap<>();
                for (String code : targetCodes) {
                    // 5. éåŽ†å‚æ•°æ•°ç»„ï¼ŒåŒ¹é…ç›®æ ‡paramCode
                    for (int i = 0; i < paramList.size(); i++) {
                        JSONObject paramObj = paramList.getJSONObject(i);
                        String currentCode = paramObj.getString("paramCode");
                        if (code.equals(currentCode)) {
                            // 6. æå–核心字段(值、单位、探头名称)
                            String paramName = paramObj.getString("paramName"); // æŽ¢å¤´1/探头2...
                            String value = paramObj.getString("value");       // æ•°å€¼ï¼ˆ345.80/24.90...)
                            String unitCode = paramObj.getString("unitCode"); // å•位(Lux/℃/%RH/ppm)
                            // 7. ä¸šåŠ¡å¤„ç†ï¼šæ‰“å°/赋值/存储等(按需修改)
                            System.out.println(paramName + ":" + value + " " + unitCode);
                            // åŒ¹é…åˆ°åŽç›´æŽ¥è·³å‡ºå†…层循环,提升效率
                JSONObject paramObj = getProbeParam(paramList, code);
                if (paramObj.isEmpty()) {
                    continue;
                }
                String paramName = paramObj.getString("paramName");
                String value = paramObj.getString("value");
                String unitCode = paramObj.getString("unitCode");
                log.info("{}: {} {}", paramName, value, unitCode);
                switch (paramName) {
                    case "探头1":
                        deviceData.put("light", value + unitCode);
                        break;
                    case "探头2":
                        deviceData.put("temperature", value + unitCode);
                        break;
                    case "探头3":
                        deviceData.put("humidity", value + unitCode);
                        break;
                    case "探头4":
                        deviceData.put("co2", value + unitCode);
                        break;
                    default:
                            break;
                        }
                    }
            listMaps.add(deviceData);
                }
        return listMaps;
            }
        }
    public static void main(String[] args) {
        System.out.println();
    }
    public static String getToken(){
    public String getToken() {
        String cachedToken = sanitizeToken(redisTemplate.opsForValue().get("JCLY_TOKEN:"));
        if (cachedToken != null) {
            return cachedToken;
        }
        Map<String, String> param = new HashMap<>();
        param.put("keyId", KET_ID);
        param.put("keySecret", KEY_SECRET);
@@ -104,27 +127,31 @@
        String result = HttpUtils.sendPostJson(URL + TOKEN_URL, JSON.toJSONString(param));
        log.info("返回结果:{}", result);
        Map<String, Object> map = JSON.parseObject(result, Map.class);
        if (map.get("code").equals(0)) {
        if (Integer.valueOf(0).equals(map.get("code"))) {
            Object token = map.get("data");
            log.info("token:{}", token);
            redisTemplate.opsForValue().set("JCLY_TOKEN:", token.toString(), 60 * 60 * 24);
            return token.toString();
        }
        return result;
    }
    public static String getRealTimeData(String  token){
    private String sanitizeToken(String token) {
        if (token == null) {
            return null;
        }
        String cleanedToken = token.replace("\0", "").trim();
        return cleanedToken.isEmpty() ? null : cleanedToken;
    }
    public static String getRealTimeData(String token, List<String> guidList) {
        Map<String, Object> param = new HashMap<>();
        param.put("keyId", KET_ID);
        param.put("keySecret", KEY_SECRET);
        param.put("deviceGuids", Collections.singletonList(DEVICE_GUID));
        param.put("deviceGuids", guidList);
        log.info("请求参数:{}", JSON.toJSONString( param));
        String result = HttpUtils.sendPostJson(URL + REAL_TIME_URL, JSON.toJSONString(param),token);
        log.info("返回结果:{}", result);
        return result;
    }
}
src/main/java/com/ruoyi/inspectiontask/pojo/InspectionTask.java
@@ -1,6 +1,10 @@
package com.ruoyi.inspectiontask.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 com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.framework.aspectj.lang.annotation.Excel;
import io.swagger.annotations.ApiModel;
@@ -21,9 +25,6 @@
    private static final long serialVersionUID = 1L;
    /**
     * å·¡æ£€ä»»åŠ¡å”¯ä¸€æ ‡è¯†
     */
    @TableId(type = IdType.AUTO)
    private Long id;
@@ -33,6 +34,13 @@
    @ApiModelProperty(value = "设备id")
    private Integer taskId;
    @ApiModelProperty(value = "设备区域id")
    private Long areaId;
    @TableField(exist = false)
    @ApiModelProperty(value = "设备区域名称")
    private String areaName;
    @ApiModelProperty(value = "巡检人ID")
    private String inspectorId;
@@ -63,7 +71,7 @@
    @ApiModelProperty(value = "巡检地点详细描述")
    private String inspectionLocation;
    @ApiModelProperty(value = "软删除标志,0=未删除,1=已删除")
    @ApiModelProperty(value = "软删除标记,0=未删除,1=已删除")
    private Integer deleted;
    @ApiModelProperty(value = "创建该记录的用户")
src/main/java/com/ruoyi/inspectiontask/pojo/TimingTask.java
@@ -1,5 +1,6 @@
package com.ruoyi.inspectiontask.pojo;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
@@ -23,9 +24,6 @@
    private static final long serialVersionUID = 1L;
    /**
     * ä¸»é”®ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;
@@ -35,6 +33,16 @@
    @ApiModelProperty(value = "设备id")
    private Integer taskId;
    @ApiModelProperty(value = "设备区域id")
    private Long areaId;
    @TableField(exist = false)
    @ApiModelProperty(value = "设备区域名称")
    private String areaName;
    @ApiModelProperty(value = "设备ID集合")
    private String taskIdsStr;
    @ApiModelProperty(value = "巡检人")
    @Excel(name = "执行巡检人")
@@ -78,32 +86,33 @@
    @ApiModelProperty(value = "状态")
    private String status;
    @ApiModelProperty(value = "软删除标志,0=未删除,1=已删除")
    @ApiModelProperty(value = "软删除标记,0=未删除,1=已删除")
    private Integer deleted;
    @TableField(exist = false)
    private String dateStr;
    @TableField(exist = false)
    @ApiModelProperty(value = "多选设备ID")
    private Long[] taskIds;
    @ApiModelProperty(value = "创建该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    @TableField(fill = FieldFill.INSERT)
    private Integer createUser;
    @ApiModelProperty(value = "记录创建时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
//    @JsonFormat(pattern = "yyyy-MM-dd")
//    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    @ApiModelProperty(value = "最后修改该记录的用户")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Integer updateUser;
    @ApiModelProperty(value = "记录最后更新时间")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT_UPDATE)
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;
    @ApiModelProperty(value = "租户ID")
    @TableField(fill = com.baomidou.mybatisplus.annotation.FieldFill.INSERT)
    @TableField(fill = FieldFill.INSERT)
    private Long tenantId;
}
src/main/java/com/ruoyi/inspectiontask/service/impl/InspectionTaskServiceImpl.java
@@ -16,6 +16,10 @@
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.device.mapper.DeviceAreaMapper;
import com.ruoyi.device.mapper.DeviceLedgerMapper;
import com.ruoyi.device.pojo.DeviceArea;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.inspectiontask.dto.InspectionTaskDto;
import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
import com.ruoyi.inspectiontask.pojo.InspectionTask;
@@ -72,12 +76,21 @@
    @Autowired
    private CommonFileServiceImpl commonFileService;
    @Autowired
    private DeviceLedgerMapper deviceLedgerMapper;
    @Autowired
    private DeviceAreaMapper deviceAreaMapper;
    @Override
    public IPage<InspectionTaskDto> selectInspectionTaskList(Page<InspectionTask> page, InspectionTaskDto inspectionTaskDto) {
        LambdaQueryWrapper<InspectionTask> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByDesc(InspectionTask::getCreateTime);
        if (StringUtils.isNotBlank(inspectionTaskDto.getTaskName())) {
            queryWrapper.like(InspectionTask::getTaskName, inspectionTaskDto.getTaskName());
        }
        if (inspectionTaskDto.getAreaId() != null) {
            queryWrapper.eq(InspectionTask::getAreaId, inspectionTaskDto.getAreaId());
        }
        IPage<InspectionTask> entityPage = inspectionTaskMapper.selectPage(page, queryWrapper);
@@ -87,6 +100,10 @@
        }
        // èŽ·å–id集合
        List<Long> ids = entityPage.getRecords().stream().map(InspectionTask::getId).collect(Collectors.toList());
        Map<Long, String> areaNameMap = buildAreaNameMap(entityPage.getRecords().stream()
                .map(InspectionTask::getAreaId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet()));
        //登记人ids
        List<Long> registrantIds = entityPage.getRecords().stream().map(InspectionTask::getRegistrantId).collect(Collectors.toList());
        // æ‰¹é‡æŸ¥è¯¢ç™»è®°äºº
@@ -143,6 +160,7 @@
            if (sysUser != null) {
                dto.setRegistrant(sysUser.getNickName());
            }
            dto.setAreaName(areaNameMap.get(inspectionTask.getAreaId()));
            // å¤„理巡检人名称
            if (StringUtils.isNotBlank(inspectionTask.getInspectorId())) {
                String inspectorNames = Arrays.stream(inspectionTask.getInspectorId().split(","))
@@ -177,6 +195,14 @@
        return resultPage;
    }
    private Map<Long, String> buildAreaNameMap(Set<Long> areaIds) {
        if (areaIds == null || areaIds.isEmpty()) {
            return Collections.emptyMap();
        }
        return deviceAreaMapper.selectBatchIds(new ArrayList<>(areaIds)).stream()
                .collect(Collectors.toMap(DeviceArea::getId, DeviceArea::getAreaName, (left, right) -> left));
    }
    // æå–创建BlobDTO的公共方法
    private StorageBlobDTO createBlobDto(StorageBlob blob) {
        StorageBlobDTO dto = new StorageBlobDTO();
@@ -204,6 +230,12 @@
    public int addOrEditInspectionTask(InspectionTaskDto inspectionTaskDto) throws IOException {
        InspectionTask inspectionTask = new InspectionTask();
        BeanUtils.copyProperties(inspectionTaskDto, inspectionTask);
        if (inspectionTask.getAreaId() == null && inspectionTask.getTaskId() != null) {
            DeviceLedger deviceLedger = deviceLedgerMapper.selectById(Long.valueOf(inspectionTask.getTaskId()));
            if (deviceLedger != null) {
                inspectionTask.setAreaId(deviceLedger.getAreaId());
            }
        }
        inspectionTask.setRegistrantId(SecurityUtils.getLoginUser().getUserId());
        inspectionTask.setRegistrant(SecurityUtils.getLoginUser().getUsername());
        int i;
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskJob.java
@@ -1,29 +1,41 @@
package com.ruoyi.inspectiontask.service.impl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.device.mapper.DeviceLedgerMapper;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
import com.ruoyi.inspectiontask.mapper.TimingTaskMapper;
import com.ruoyi.inspectiontask.pojo.InspectionTask;
import com.ruoyi.inspectiontask.pojo.TimingTask;
import com.ruoyi.inspectiontask.service.TimingTaskService;
import org.quartz.*;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.DayOfWeek;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
@Component
@DisallowConcurrentExecution // ç¦æ­¢å¹¶å‘执行同一个Job
@DisallowConcurrentExecution
public class TimingTaskJob implements Job, Serializable {
    private static final long serialVersionUID = 1L; // å¿…须定义序列化ID
    private static final long serialVersionUID = 1L;
    @Autowired
    private TimingTaskMapper timingTaskMapper;
@@ -37,18 +49,18 @@
    @Autowired
    private JdbcTemplate jdbcTemplate;
    @Autowired
    private DeviceLedgerMapper deviceLedgerMapper;
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
        // ä¿®å¤ç±»åž‹è½¬æ¢é”™è¯¯ï¼Œæ­£ç¡®èŽ·å–taskId
        Long taskId = jobDataMap.getLong("taskId");
        try {
            // 3. å°è¯•查询你的业务数据
            // é€šè¿‡JDBC模板查询定时任务信息,使用参数化查询防止SQL注入
            String yourSql = "SELECT * FROM timing_task where id = ?";
            String sql = "SELECT * FROM timing_task WHERE id = ?";
            List<TimingTask> tasks = jdbcTemplate.query(
                    yourSql,
                    sql,
                    new BeanPropertyRowMapper<>(TimingTask.class),
                    taskId
            );
@@ -57,73 +69,76 @@
                throw new JobExecutionException("找不到定时任务: " + taskId);
            }
            
//            if (!timingTask.isActive()) {
//                throw new JobExecutionException("定时任务已禁用: " + taskId);
//            }
            List<Long> deviceIds = resolveTaskIds(timingTask);
            if (deviceIds.isEmpty()) {
                throw new JobExecutionException("定时任务未配置设备: " + taskId);
            }
            // 2. åˆ›å»ºå¹¶ä¿å­˜å·¡æ£€ä»»åŠ¡è®°å½• - è¿™å°±æ˜¯æ‚¨æä¾›çš„代码应该放的位置
            InspectionTask inspectionTask = createInspectionTask(timingTask);
            inspectionTaskMapper.insert(inspectionTask);
            for (Long deviceId : deviceIds) {
                DeviceLedger deviceLedger = deviceLedgerMapper.selectById(deviceId);
                if (deviceLedger == null) {
                    continue;
                }
                int count = getDeviceCount(deviceLedger.getNumber());
                for (int i = 0; i < count; i++) {
                    inspectionTaskMapper.insert(createInspectionTask(timingTask, deviceLedger));
                }
            }
            // 3. æ›´æ–°å®šæ—¶ä»»åŠ¡çš„æ‰§è¡Œæ—¶é—´
            if (!tasks.isEmpty()) {
                TimingTask task = tasks.get(0);
                // æ›´æ–°æœ€åŽæ‰§è¡Œæ—¶é—´ä¸ºå½“前时间
                LocalDateTime lastExecutionTime = LocalDateTime.now();
                // è®¡ç®—下次执行时间
                LocalDateTime nextExecutionTime = calculateNextExecutionTime(
                        task.getFrequencyType(),
                        task.getFrequencyDetail(),
                        timingTask.getFrequencyType(),
                        timingTask.getFrequencyDetail(),
                        lastExecutionTime
                );
                // æ‰§è¡Œæ›´æ–°æ“ä½œ
                String updateSql = "UPDATE timing_task " +
                        "SET last_execution_time = ?, next_execution_time = ? " +
                        "WHERE id = ?";
                jdbcTemplate.update(
                        updateSql,
                        lastExecutionTime,
                        nextExecutionTime,
                        taskId
                );
                String updateSql = "UPDATE timing_task SET last_execution_time = ?, next_execution_time = ? WHERE id = ?";
                jdbcTemplate.update(updateSql, lastExecutionTime, nextExecutionTime, taskId);
            }
//            timingTaskService.updateTaskExecutionTime(taskId);
            // 4. è®°å½•执行日志
//            timingTaskService.recordExecutionLog(taskId, true, "任务执行成功,生成巡检任务ID: " + inspectionTask.getId());
        } catch (Exception e) {
//            timingTaskService.recordExecutionLog(taskId, false, "任务执行失败: " + e.getMessage());
            throw new JobExecutionException(e);
        }
    }
    // è¿™å°±æ˜¯æ‚¨æä¾›çš„代码封装成的方法
    private InspectionTask createInspectionTask(TimingTask timingTask) {
        InspectionTask inspectionTask = new InspectionTask();
    private List<Long> resolveTaskIds(TimingTask timingTask) {
        if (StringUtils.isNotBlank(timingTask.getTaskIdsStr())) {
            return Arrays.stream(timingTask.getTaskIdsStr().split(","))
                    .filter(StringUtils::isNotBlank)
                    .map(String::trim)
                    .map(Long::valueOf)
                    .distinct()
                    .collect(Collectors.toList());
        }
        if (timingTask.getTaskId() != null) {
            return new ArrayList<>(Arrays.asList(timingTask.getTaskId().longValue()));
        }
        return new ArrayList<>();
    }
        // å¤åˆ¶åŸºæœ¬å±žæ€§
        inspectionTask.setTaskName(timingTask.getTaskName());
        inspectionTask.setTaskId(timingTask.getTaskId());
    private int getDeviceCount(BigDecimal number) {
        if (number == null) {
            return 1;
        }
        int count = number.intValue();
        return count > 0 ? count : 1;
    }
    private InspectionTask createInspectionTask(TimingTask timingTask, DeviceLedger deviceLedger) {
        InspectionTask inspectionTask = new InspectionTask();
        inspectionTask.setTaskName(deviceLedger.getDeviceName());
        inspectionTask.setTaskId(deviceLedger.getId().intValue());
        inspectionTask.setAreaId(deviceLedger.getAreaId());
        inspectionTask.setInspectorId(timingTask.getInspectorIds());
        inspectionTask.setInspectionLocation(timingTask.getInspectionLocation());
        inspectionTask.setRemarks("自动生成自定时任务ID: " + timingTask.getId());
        inspectionTask.setRegistrantId(timingTask.getRegistrantId());
        inspectionTask.setRegistrant(timingTask.getRegistrant());
        inspectionTask.setFrequencyType(timingTask.getFrequencyType());
        inspectionTask.setFrequencyDetail(timingTask.getFrequencyDetail());
        inspectionTask.setTenantId(timingTask.getTenantId());
        return inspectionTask;
    }
    /**
     * è®¡ç®—下次执行时间
     */
    private LocalDateTime calculateNextExecutionTime(String frequencyType,
                                                     String frequencyDetail,
                                                     LocalDateTime currentTime) {
@@ -145,86 +160,54 @@
        }
    }
    /**
     * è®¡ç®—每日任务的下次执行时间
     */
    private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime current) {
        LocalTime executionTime = LocalTime.parse(timeStr); // è§£æžæ ¼å¼ "HH:mm"
        LocalTime executionTime = LocalTime.parse(timeStr);
        LocalDateTime nextTime = LocalDateTime.of(current.toLocalDate(), executionTime);
        // å¦‚果今天的时间已过,则安排明天
        return current.isBefore(nextTime) ? nextTime : nextTime.plusDays(1);
    }
    /**
     * è®¡ç®—每周任务的下次执行时间
     */
    private LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        String dayOfWeekStr = parts[0];  // å¦‚ "MON" æˆ– "MON|WED|FRI"
        LocalTime time = LocalTime.parse(parts[1]); // æ—¶é—´éƒ¨åˆ†
        // è§£æžæ˜ŸæœŸå‡ (支持多个星期)
        String dayOfWeekStr = parts[0];
        LocalTime time = LocalTime.parse(parts[1]);
        Set<DayOfWeek> targetDays = parseDayOfWeeks(dayOfWeekStr);
        // ä»Žå½“前时间开始找下一个符合条件的星期几
        LocalDateTime nextTime = current;
        while (true) {
            nextTime = nextTime.plusDays(1);
            if (targetDays.contains(nextTime.getDayOfWeek())) {
                return LocalDateTime.of(nextTime.toLocalDate(), time);
            }
            // é˜²æ­¢æ— é™å¾ªçޝ(理论上不会发生)
            if (nextTime.isAfter(current.plusYears(1))) {
                throw new RuntimeException("无法找到下次执行时间");
            }
        }
    }
    /**
     * è®¡ç®—每月任务的下次执行时间
     */
    private LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int dayOfMonth = Integer.parseInt(parts[0]);
        LocalTime time = LocalTime.parse(parts[1]);
        // ä»Žä¸‹ä¸ªæœˆå¼€å§‹è®¡ç®—
        LocalDateTime nextTime = current.plusMonths(1)
        return current.plusMonths(1)
                .withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).toLocalDate().lengthOfMonth()))
                .with(time);
        return nextTime;
    }
    /**
     * è®¡ç®—每季度任务的下次执行时间
     */
    private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int quarterMonth = Integer.parseInt(parts[0]); // 1=第1个月,2=第2个月,3=第3个月
        int quarterMonth = Integer.parseInt(parts[0]);
        int dayOfMonth = Integer.parseInt(parts[1]);
        LocalTime time = LocalTime.parse(parts[2]);
        // è®¡ç®—当前季度
        int currentQuarter = (current.getMonthValue() - 1) / 3 + 1;
        int currentMonthInQuarter = (current.getMonthValue() - 1) % 3 + 1;
        YearMonth targetYearMonth;
        if (currentMonthInQuarter < quarterMonth) {
            // æœ¬å­£åº¦å†…还有执行机会
            targetYearMonth = YearMonth.from(current)
                    .plusMonths(quarterMonth - currentMonthInQuarter);
            targetYearMonth = YearMonth.from(current).plusMonths(quarterMonth - currentMonthInQuarter);
        } else {
            // éœ€è¦åˆ°ä¸‹ä¸ªå­£åº¦
            targetYearMonth = YearMonth.from(current)
                    .plusMonths(3 - currentMonthInQuarter + quarterMonth);
            targetYearMonth = YearMonth.from(current).plusMonths(3 - currentMonthInQuarter + quarterMonth);
        }
        // å¤„理月末日期
        int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
        return LocalDateTime.of(
                targetYearMonth.getYear(),
                targetYearMonth.getMonthValue(),
@@ -234,23 +217,35 @@
        );
    }
    /**
     * è§£æžæ˜ŸæœŸå‡ å­—符串
     */
    private Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) {
        Set<DayOfWeek> days = new HashSet<>();
        String[] dayStrs = dayOfWeekStr.split("\\|");
        for (String dayStr : dayStrs) {
            switch (dayStr) {
                case "MON": days.add(DayOfWeek.MONDAY); break;
                case "TUE": days.add(DayOfWeek.TUESDAY); break;
                case "WED": days.add(DayOfWeek.WEDNESDAY); break;
                case "THU": days.add(DayOfWeek.THURSDAY); break;
                case "FRI": days.add(DayOfWeek.FRIDAY); break;
                case "SAT": days.add(DayOfWeek.SATURDAY); break;
                case "SUN": days.add(DayOfWeek.SUNDAY); break;
                default: throw new IllegalArgumentException("无效的星期几: " + dayStr);
                case "MON":
                    days.add(DayOfWeek.MONDAY);
                    break;
                case "TUE":
                    days.add(DayOfWeek.TUESDAY);
                    break;
                case "WED":
                    days.add(DayOfWeek.WEDNESDAY);
                    break;
                case "THU":
                    days.add(DayOfWeek.THURSDAY);
                    break;
                case "FRI":
                    days.add(DayOfWeek.FRIDAY);
                    break;
                case "SAT":
                    days.add(DayOfWeek.SATURDAY);
                    break;
                case "SUN":
                    days.add(DayOfWeek.SUNDAY);
                    break;
                default:
                    throw new IllegalArgumentException("无效的星期: " + dayStr);
            }
        }
src/main/java/com/ruoyi/inspectiontask/service/impl/TimingTaskServiceImpl.java
@@ -6,6 +6,10 @@
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.bean.BeanUtils;
import com.ruoyi.device.mapper.DeviceAreaMapper;
import com.ruoyi.device.mapper.DeviceLedgerMapper;
import com.ruoyi.device.pojo.DeviceArea;
import com.ruoyi.device.pojo.DeviceLedger;
import com.ruoyi.inspectiontask.dto.TimingTaskDto;
import com.ruoyi.inspectiontask.mapper.InspectionTaskMapper;
import com.ruoyi.inspectiontask.mapper.TimingTaskMapper;
@@ -19,9 +23,23 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.*;
import java.time.DayOfWeek;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -44,35 +62,34 @@
    @Autowired
    private SysUserMapper sysUserMapper;
    @Autowired
    private DeviceLedgerMapper deviceLedgerMapper;
    @Autowired
    private DeviceAreaMapper deviceAreaMapper;
    @Override
    public IPage<TimingTaskDto> selectTimingTaskList(Page<TimingTask> page, TimingTask timingTask) {
        // 1. å…ˆåˆ†é¡µæŸ¥è¯¢å®šæ—¶ä»»åŠ¡æ•°æ®
        // æž„建查询条件
        LambdaQueryWrapper<TimingTask> queryWrapper = new LambdaQueryWrapper<>();
        if (StringUtils.isNotBlank(timingTask.getTaskName())) {
            queryWrapper.like(TimingTask::getTaskName, timingTask.getTaskName());
        }
        if (timingTask.getAreaId() != null) {
            queryWrapper.eq(TimingTask::getAreaId, timingTask.getAreaId());
        }
        IPage<TimingTask> taskPage = timingTaskMapper.selectPage(page, queryWrapper);
        // 2. å¦‚果没有数据,直接返回空分页
        if (taskPage.getRecords().isEmpty()) {
            return new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
        }
        // 3. æ”¶é›†æ‰€æœ‰éœ€è¦æŸ¥è¯¢çš„用户ID
        Set<Long> userIds = new HashSet<>();
        // æ”¶é›†ç™»è®°äººID
        taskPage.getRecords().forEach(task -> {
            if (task.getRegistrantId() != null) {
                userIds.add(task.getRegistrantId());
            }
        });
        // æ”¶é›†å·¡æ£€äººID(多个ID以逗号分隔)
        taskPage.getRecords().forEach(task -> {
            if (task.getCreateTime() != null) {
            task.setDateStr(task.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
            }
            if (StringUtils.isNotBlank(task.getInspectorIds())) {
                Arrays.stream(task.getInspectorIds().split(","))
                        .filter(StringUtils::isNotBlank)
@@ -81,25 +98,27 @@
            }
        });
        // 4. æ‰¹é‡æŸ¥è¯¢ç”¨æˆ·ä¿¡æ¯
        Map<Long, String> userNickNameMap = new HashMap<>();
        if (!userIds.isEmpty()) {
            List<SysUser> users = sysUserMapper.selectUserByIds((new ArrayList<>(userIds)));
            List<SysUser> users = sysUserMapper.selectUserByIds(new ArrayList<>(userIds));
            users.forEach(user -> userNickNameMap.put(user.getUserId(), user.getNickName()));
        }
        // 5. è½¬æ¢ä¸ºDTO
        Map<Long, String> areaNameMap = deviceAreaMapper.selectBatchIds(taskPage.getRecords().stream()
                        .map(TimingTask::getAreaId)
                        .filter(Objects::nonNull)
                        .distinct()
                        .collect(Collectors.toList()))
                .stream()
                .collect(Collectors.toMap(DeviceArea::getId, DeviceArea::getAreaName, (left, right) -> left, HashMap::new));
        List<TimingTaskDto> dtoList = taskPage.getRecords().stream().map(task -> {
            TimingTaskDto dto = new TimingTaskDto();
            // å¤åˆ¶åŸºæœ¬å±žæ€§
            BeanUtils.copyProperties(task, dto);
            // è®¾ç½®ç™»è®°äººæ˜µç§°
            if (task.getRegistrantId() != null) {
                dto.setRegistrant(userNickNameMap.getOrDefault(task.getRegistrantId(), "未知用户"));
            }
            // è®¾ç½®å·¡æ£€äººæ˜µç§°åˆ—表
            dto.setAreaName(areaNameMap.get(task.getAreaId()));
            if (StringUtils.isNotBlank(task.getInspectorIds())) {
                List<String> inspectorNickNames = new ArrayList<>();
                for (String idStr : task.getInspectorIds().split(",")) {
@@ -110,11 +129,9 @@
                }
                dto.setInspector(inspectorNickNames);
            }
            return dto;
        }).collect(Collectors.toList());
        // 6. æž„建返回的分页对象
        Page<TimingTaskDto> resultPage = new Page<>(taskPage.getCurrent(), taskPage.getSize(), taskPage.getTotal());
        resultPage.setRecords(dtoList);
        return resultPage;
@@ -125,58 +142,90 @@
    public int addOrEditTimingTask(TimingTaskDto timingTaskDto) throws SchedulerException {
        TimingTask timingTask = new TimingTask();
        BeanUtils.copyProperties(timingTaskDto, timingTask);
        // 1. è§£æžå­—符串为 LocalDate(只包含年月日)
        prepareTimingTask(timingTask);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        LocalDate localDate = LocalDate.now();
        if(StringUtils.isNotEmpty(timingTaskDto.getDateStr())){
            localDate = LocalDate.parse(timingTaskDto.getDateStr(), formatter);
        }
        // 2. èŽ·å–å½“å‰ç³»ç»Ÿçš„ LocalTime(包含时分秒)
        LocalTime currentTime = LocalTime.now();
        timingTask.setCreateTime(LocalDateTime.of(localDate, currentTime));
        // 3. åˆå¹¶ LocalDate å’Œå½“前 LocalTime ä¸º LocalDateTime
        LocalDateTime localDateTime = LocalDateTime.of(localDate, currentTime);
        timingTask.setCreateTime(localDateTime);
        // è®¾ç½®åˆ›å»ºäººä¿¡æ¯å’Œé»˜è®¤å€¼
        if (Objects.isNull(timingTaskDto.getId())) {
            timingTask.setRegistrationDate(LocalDate.now());
            timingTask.setActive(true);
            // è®¡ç®—首次执行时间
            LocalDateTime firstExecutionTime = calculateFirstExecutionTime(timingTask);
            timingTask.setNextExecutionTime(firstExecutionTime);
            timingTask.setNextExecutionTime(calculateFirstExecutionTime(timingTask));
            int result = timingTaskMapper.insert(timingTask);
            if (result > 0) {
                // æ–°å¢žæˆåŠŸåŽæ·»åŠ åˆ°è°ƒåº¦å™¨
                timingTaskScheduler.scheduleTimingTask(timingTask);
            }
            return result;
        } else {
        }
            int result = timingTaskMapper.updateById(timingTask);
            if (result > 0) {
                // æ›´æ–°æˆåŠŸåŽé‡æ–°è°ƒåº¦ä»»åŠ¡
                timingTaskScheduler.rescheduleTimingTask(timingTask);
            }
            return result;
        }
    private void prepareTimingTask(TimingTask timingTask) {
        Long[] selectedTaskIds = timingTask.getTaskIds();
        if ((selectedTaskIds == null || selectedTaskIds.length == 0) && timingTask.getTaskId() != null) {
            selectedTaskIds = new Long[]{timingTask.getTaskId().longValue()};
        }
        if (selectedTaskIds == null || selectedTaskIds.length == 0) {
            timingTask.setTaskIdsStr(null);
            return;
        }
        List<Long> deviceIds = Arrays.stream(selectedTaskIds)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (deviceIds.isEmpty()) {
            timingTask.setTaskIdsStr(null);
            return;
        }
        List<DeviceLedger> devices = deviceLedgerMapper.selectBatchIds(deviceIds);
        Map<Long, DeviceLedger> deviceMap = devices.stream()
                .collect(Collectors.toMap(DeviceLedger::getId, device -> device, (left, right) -> left, LinkedHashMap::new));
        List<Long> validDeviceIds = deviceIds.stream()
                .filter(deviceMap::containsKey)
                .collect(Collectors.toList());
        if (validDeviceIds.isEmpty()) {
            throw new IllegalArgumentException("所选设备不存在");
        }
        timingTask.setTaskIds(validDeviceIds.toArray(new Long[0]));
        timingTask.setTaskIdsStr(validDeviceIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
        timingTask.setTaskId(validDeviceIds.get(0).intValue());
        if (timingTask.getAreaId() == null) {
            DeviceLedger firstDevice = deviceMap.get(validDeviceIds.get(0));
            if (firstDevice != null) {
                timingTask.setAreaId(firstDevice.getAreaId());
            }
        }
        timingTask.setTaskName(validDeviceIds.stream()
                .map(deviceMap::get)
                .filter(Objects::nonNull)
                .map(DeviceLedger::getDeviceName)
                .filter(StringUtils::isNotBlank)
                .collect(Collectors.joining(",")));
    }
    public LocalDateTime calculateFirstExecutionTime(TimingTask task) {
        // æ ¹æ®é¢‘率类型和详情计算首次执行时间
        String frequencyType = task.getFrequencyType();
        if ("DAILY".equals(frequencyType)) {
            // å¦‚果是每天执行,计算今天或明天的具体时间
            return calculateDailyFirstExecution(task.getFrequencyDetail());
        } else if ("WEEKLY".equals(frequencyType)) {
            // å¦‚果是每周执行,计算下周的具体星期几
            return calculateWeeklyFirstExecution(task.getFrequencyDetail());
        } else if ("MONTHLY".equals(frequencyType)) {
            // å¦‚果是每月执行,计算下个月的具体日期
            return calculateMonthlyFirstExecution(task.getFrequencyDetail());
        } else if ("QUARTERLY".equals(frequencyType)) {
            // è‡ªå®šä¹‰é¢‘率,如每小时、每30分钟等
            return calculateCustomFirstExecution(task.getFrequencyDetail());
        } else {
            throw new IllegalArgumentException("不支持的频率类型: " + task.getFrequencyType());
@@ -184,17 +233,14 @@
    }
    private LocalDateTime calculateDailyFirstExecution(String frequencyDetail) {
        // frequencyDetail可能是具体时间,如 "14:30"
        LocalTime executionTime = LocalTime.parse(frequencyDetail);
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime todayExecution = LocalDateTime.of(now.toLocalDate(), executionTime);
        // å¦‚果今天的时间已过,则安排明天执行
        return now.isBefore(todayExecution) ? todayExecution : todayExecution.plusDays(1);
    }
    // æ˜ å°„星期简写与DayOfWeek
    private static final Map<String, DayOfWeek> WEEK_DAY_MAP = new HashMap<>();
    static {
        WEEK_DAY_MAP.put("MON", DayOfWeek.MONDAY);
        WEEK_DAY_MAP.put("TUE", DayOfWeek.TUESDAY);
@@ -206,47 +252,36 @@
    }
    private LocalDateTime calculateWeeklyFirstExecution(String frequencyDetail) {
        // è§£æžè¾“入参数
        String[] parts = frequencyDetail.split(",");
        if (parts.length != 2) {
            throw new IllegalArgumentException("参数格式错误,应为'MON,13:43'格式");
            throw new IllegalArgumentException("参数格式错误,应为 MON,13:43");
        }
        String weekDayStr = parts[0].trim();
        String timeStr = parts[1].trim();
        // èŽ·å–å¯¹åº”çš„æ˜ŸæœŸå‡ 
        DayOfWeek targetDay = WEEK_DAY_MAP.get(weekDayStr);
        if (targetDay == null) {
            throw new IllegalArgumentException("无效的星期简写: " + weekDayStr);
            throw new IllegalArgumentException("无效的星期: " + weekDayStr);
        }
        // è§£æžæ—¶é—´
        LocalTime targetTime = LocalTime.parse(timeStr, DateTimeFormatter.ofPattern("HH:mm"));
        // èŽ·å–å½“å‰æ—¶é—´
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime targetDateTime = now.with(targetDay).with(targetTime);
        // å¦‚果计算出的时间在当前时间之前,则加一周
        if (targetDateTime.isBefore(now)) {
            targetDateTime = targetDateTime.plusWeeks(1);
        }
        return targetDateTime;
    }
    private LocalDateTime calculateMonthlyFirstExecution(String frequencyDetail) {
        // è§£æžè¾“入参数
        String[] parts = frequencyDetail.split(",");
        if (parts.length != 2) {
            throw new IllegalArgumentException("参数格式错误,应为'03,17:00'格式");
            throw new IllegalArgumentException("参数格式错误,应为 03,17:00");
        }
        String dayStr = parts[0].trim();
        String timeStr = parts[1].trim();
        // è§£æžæ—¥æœŸ
        int dayOfMonth;
        try {
            dayOfMonth = Integer.parseInt(dayStr);
@@ -254,12 +289,10 @@
            throw new IllegalArgumentException("无效的日期格式: " + dayStr, e);
        }
        // éªŒè¯æ—¥æœŸæœ‰æ•ˆæ€§ï¼ˆ1-31之间)
        if (dayOfMonth < 1 || dayOfMonth > 31) {
            throw new IllegalArgumentException("日期必须在1-31之间: " + dayOfMonth);
        }
        // è§£æžæ—¶é—´
        LocalTime targetTime;
        try {
            targetTime = LocalTime.parse(timeStr, DateTimeFormatter.ofPattern("HH:mm"));
@@ -267,32 +300,18 @@
            throw new IllegalArgumentException("无效的时间格式: " + timeStr, e);
        }
        // èŽ·å–å½“å‰æ—¶é—´
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime targetDateTime = now.withDayOfMonth(dayOfMonth).with(targetTime);
        // æ£€æŸ¥æ—¥æœŸæ˜¯å¦è¢«è‡ªåŠ¨è°ƒæ•´ï¼ˆå¦‚31日在小月会被调整)
        boolean isDateAdjusted = targetDateTime.getDayOfMonth() != dayOfMonth;
        // å¦‚果目标时间在当前时间之前,或者日期被系统自动调整了
        if (targetDateTime.isBefore(now) || isDateAdjusted) {
            // è®¡ç®—下个月的日期
            LocalDateTime nextMonth = now.plusMonths(1);
            // å°è¯•设置下个月的目标日期
            LocalDateTime nextMonthTarget = nextMonth.withDayOfMonth(dayOfMonth).with(targetTime);
            // å¦‚果下个月的日期也被调整了,就用下个月的最后一天
            if (nextMonthTarget.getDayOfMonth() != dayOfMonth) {
                // æ­£ç¡®èŽ·å–ä¸‹ä¸ªæœˆçš„æœ€åŽä¸€å¤©ï¼ˆä¿®å¤isLeapYear调用问题)
                int lastDayOfMonth = nextMonth.getMonth().length(
                        Year.of(nextMonth.getYear()).isLeap()
                );
                int lastDayOfMonth = nextMonth.getMonth().length(Year.of(nextMonth.getYear()).isLeap());
                nextMonthTarget = nextMonth.withDayOfMonth(lastDayOfMonth).with(targetTime);
            }
            targetDateTime = nextMonthTarget;
        }
        return targetDateTime;
    }
@@ -308,24 +327,16 @@
            throw new RuntimeException("定时任务不存在,ID: " + taskId);
        }
        // æ›´æ–°æœ€åŽæ‰§è¡Œæ—¶é—´ä¸ºå½“前时间
        task.setLastExecutionTime(LocalDateTime.now());
        // è®¡ç®—下次执行时间
        LocalDateTime nextExecutionTime = calculateNextExecutionTime(
                task.getFrequencyType(),
                task.getFrequencyDetail(),
                LocalDateTime.now()
        );
        task.setNextExecutionTime(nextExecutionTime);
        // æ›´æ–°æ•°æ®åº“
        timingTaskMapper.updateById(task);
    }
    /**
     * è®¡ç®—下次执行时间
     */
    private LocalDateTime calculateNextExecutionTime(String frequencyType,
                                                     String frequencyDetail,
                                                     LocalDateTime currentTime) {
@@ -347,86 +358,54 @@
        }
    }
    /**
     * è®¡ç®—每日任务的下次执行时间
     */
    private LocalDateTime calculateDailyNextTime(String timeStr, LocalDateTime current) {
        LocalTime executionTime = LocalTime.parse(timeStr); // è§£æžæ ¼å¼ "HH:mm"
        LocalTime executionTime = LocalTime.parse(timeStr);
        LocalDateTime nextTime = LocalDateTime.of(current.toLocalDate(), executionTime);
        // å¦‚果今天的时间已过,则安排明天
        return current.isBefore(nextTime) ? nextTime : nextTime.plusDays(1);
    }
    /**
     * è®¡ç®—每周任务的下次执行时间
     */
    private LocalDateTime calculateWeeklyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        String dayOfWeekStr = parts[0];  // å¦‚ "MON" æˆ– "MON|WED|FRI"
        LocalTime time = LocalTime.parse(parts[1]); // æ—¶é—´éƒ¨åˆ†
        // è§£æžæ˜ŸæœŸå‡ (支持多个星期)
        String dayOfWeekStr = parts[0];
        LocalTime time = LocalTime.parse(parts[1]);
        Set<DayOfWeek> targetDays = parseDayOfWeeks(dayOfWeekStr);
        // ä»Žå½“前时间开始找下一个符合条件的星期几
        LocalDateTime nextTime = current;
        while (true) {
            nextTime = nextTime.plusDays(1);
            if (targetDays.contains(nextTime.getDayOfWeek())) {
                return LocalDateTime.of(nextTime.toLocalDate(), time);
            }
            // é˜²æ­¢æ— é™å¾ªçޝ(理论上不会发生)
            if (nextTime.isAfter(current.plusYears(1))) {
                throw new RuntimeException("无法找到下次执行时间");
            }
        }
    }
    /**
     * è®¡ç®—每月任务的下次执行时间
     */
    private LocalDateTime calculateMonthlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int dayOfMonth = Integer.parseInt(parts[0]);
        LocalTime time = LocalTime.parse(parts[1]);
        // ä»Žä¸‹ä¸ªæœˆå¼€å§‹è®¡ç®—
        LocalDateTime nextTime = current.plusMonths(1)
        return current.plusMonths(1)
                .withDayOfMonth(Math.min(dayOfMonth, current.plusMonths(1).toLocalDate().lengthOfMonth()))
                .with(time);
        return nextTime;
    }
    /**
     * è®¡ç®—每季度任务的下次执行时间
     */
    private LocalDateTime calculateQuarterlyNextTime(String detail, LocalDateTime current) {
        String[] parts = detail.split(",");
        int quarterMonth = Integer.parseInt(parts[0]); // 1=第1个月,2=第2个月,3=第3个月
        int quarterMonth = Integer.parseInt(parts[0]);
        int dayOfMonth = Integer.parseInt(parts[1]);
        LocalTime time = LocalTime.parse(parts[2]);
        // è®¡ç®—当前季度
        int currentQuarter = (current.getMonthValue() - 1) / 3 + 1;
        int currentMonthInQuarter = (current.getMonthValue() - 1) % 3 + 1;
        YearMonth targetYearMonth;
        if (currentMonthInQuarter < quarterMonth) {
            // æœ¬å­£åº¦å†…还有执行机会
            targetYearMonth = YearMonth.from(current)
                    .plusMonths(quarterMonth - currentMonthInQuarter);
            targetYearMonth = YearMonth.from(current).plusMonths(quarterMonth - currentMonthInQuarter);
        } else {
            // éœ€è¦åˆ°ä¸‹ä¸ªå­£åº¦
            targetYearMonth = YearMonth.from(current)
                    .plusMonths(3 - currentMonthInQuarter + quarterMonth);
            targetYearMonth = YearMonth.from(current).plusMonths(3 - currentMonthInQuarter + quarterMonth);
        }
        // å¤„理月末日期
        int adjustedDay = Math.min(dayOfMonth, targetYearMonth.lengthOfMonth());
        return LocalDateTime.of(
                targetYearMonth.getYear(),
                targetYearMonth.getMonthValue(),
@@ -436,30 +415,38 @@
        );
    }
    /**
     * è§£æžæ˜ŸæœŸå‡ å­—符串
     */
    private Set<DayOfWeek> parseDayOfWeeks(String dayOfWeekStr) {
        Set<DayOfWeek> days = new HashSet<>();
        String[] dayStrs = dayOfWeekStr.split("\\|");
        for (String dayStr : dayStrs) {
            switch (dayStr) {
                case "MON": days.add(DayOfWeek.MONDAY); break;
                case "TUE": days.add(DayOfWeek.TUESDAY); break;
                case "WED": days.add(DayOfWeek.WEDNESDAY); break;
                case "THU": days.add(DayOfWeek.THURSDAY); break;
                case "FRI": days.add(DayOfWeek.FRIDAY); break;
                case "SAT": days.add(DayOfWeek.SATURDAY); break;
                case "SUN": days.add(DayOfWeek.SUNDAY); break;
                default: throw new IllegalArgumentException("无效的星期几: " + dayStr);
                case "MON":
                    days.add(DayOfWeek.MONDAY);
                    break;
                case "TUE":
                    days.add(DayOfWeek.TUESDAY);
                    break;
                case "WED":
                    days.add(DayOfWeek.WEDNESDAY);
                    break;
                case "THU":
                    days.add(DayOfWeek.THURSDAY);
                    break;
                case "FRI":
                    days.add(DayOfWeek.FRIDAY);
                    break;
                case "SAT":
                    days.add(DayOfWeek.SATURDAY);
                    break;
                case "SUN":
                    days.add(DayOfWeek.SUNDAY);
                    break;
                default:
                    throw new IllegalArgumentException("无效的星期: " + dayStr);
            }
        }
        return days;
    }
    @Override
    public int delByIds(Long[] ids) {
@@ -471,5 +458,4 @@
        }
        return i;
    }
}
src/main/resources/mapper/device/DeviceAreaMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
<?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.device.mapper.DeviceAreaMapper">
    <select id="queryPage" resultType="com.ruoyi.device.pojo.DeviceArea">
        select
            da.*,
            parent.area_name as parentName
        from device_area da
        left join device_area parent on da.parent_id = parent.id
        <where>
            <if test="deviceArea.areaName != null and deviceArea.areaName != ''">
                and da.area_name like concat('%', #{deviceArea.areaName}, '%')
            </if>
            <if test="deviceArea.parentId != null">
                and da.parent_id = #{deviceArea.parentId}
            </if>
        </where>
        order by da.parent_id asc, da.sort asc, da.id asc
    </select>
</mapper>
src/main/resources/mapper/device/DeviceLedgerMapper.xml
@@ -10,9 +10,11 @@
        dl.id,
        dl.device_name,
        dl.device_model,
        dl.supplier_name,
        dl.device_brand,
        dl.storage_location,
            dl.area_id,
            da.area_name,
            dl.supplier_name,
        dl.unit,
        dl.number,
        dl.status,
@@ -33,60 +35,56 @@
        dl.annual_depreciation_amount,
        dl.type
        FROM device_ledger dl
        left join sys_user su on dl.create_user = su.user_id
                 LEFT JOIN sys_user su ON dl.create_user = su.user_id
                 LEFT JOIN device_area da ON dl.area_id = da.id
        <where>
            <!-- è®¾å¤‡åç§° -->
            <if test="deviceLedger.deviceName != null and deviceLedger.deviceName != ''">
                AND device_name LIKE CONCAT('%', #{deviceLedger.deviceName}, '%')
                AND dl.device_name LIKE CONCAT('%', #{deviceLedger.deviceName}, '%')
            </if>
            <!-- è§„格型号 -->
            <if test="deviceLedger.deviceModel != null and deviceLedger.deviceModel != ''">
                AND device_model LIKE CONCAT('%', #{deviceLedger.deviceModel}, '%')
                AND dl.device_model LIKE CONCAT('%', #{deviceLedger.deviceModel}, '%')
            </if>
            <!-- ä¾›åº”商名称 -->
            <if test="deviceLedger.supplierName != null and deviceLedger.supplierName != ''">
                AND supplier_name LIKE CONCAT('%', #{deviceLedger.supplierName}, '%')
                AND dl.supplier_name LIKE CONCAT('%', #{deviceLedger.supplierName}, '%')
            </if>
            <!-- å•位 -->
            <if test="deviceLedger.areaId != null">
                AND dl.area_id = #{deviceLedger.areaId}
            </if>
            <if test="deviceLedger.areaName != null and deviceLedger.areaName != ''">
                AND da.area_name LIKE CONCAT('%', #{deviceLedger.areaName}, '%')
            </if>
            <if test="deviceLedger.unit != null and deviceLedger.unit != ''">
                AND unit = #{deviceLedger.unit}
                AND dl.unit = #{deviceLedger.unit}
            </if>
            <!-- å½•入人 -->
            <if test="deviceLedger.createUser != null and deviceLedger.createUser != ''">
                AND create_user LIKE CONCAT('%', #{deviceLedger.createUser}, '%')
                AND dl.create_user LIKE CONCAT('%', #{deviceLedger.createUser}, '%')
            </if>
            <!-- æ›´æ–°äºº -->
            <if test="deviceLedger.updateUser != null and deviceLedger.updateUser != ''">
                AND update_user LIKE CONCAT('%', #{deviceLedger.updateUser}, '%')
                AND dl.update_user LIKE CONCAT('%', #{deviceLedger.updateUser}, '%')
            </if>
            <if test="deviceLedger.entryDateStart != null and deviceLedger.entryDateStart != '' ">
                AND dl.create_time &gt;= DATE_FORMAT(#{deviceLedger.entryDateStart},'%Y-%m-%d')
            </if>
            <if test="deviceLedger.entryDateEnd != null and deviceLedger.entryDateEnd != '' ">
                AND dl.create_time &lt;= DATE_FORMAT(#{deviceLedger.entryDateEnd},'%Y-%m-%d')
            </if>
            <!-- ç§Ÿæˆ·ID -->
            <if test="deviceLedger.tenantId != null">
                AND tenant_id = #{deviceLedger.tenantId}
                AND dl.tenant_id = #{deviceLedger.tenantId}
            </if>
        </where>
        ORDER BY create_time DESC
        ORDER BY dl.create_time DESC
    </select>
    <select id="deviceLedgerExportList" resultType="com.ruoyi.device.execl.DeviceLedgerExeclDto">
    </select>
    <select id="selectById1" resultType="com.ruoyi.device.pojo.DeviceLedger">
        select *
        from device_ledger
        where id = #{id}
    </select>
    <select id="getDeviceTypeDistributionByYear"
            resultType="com.ruoyi.account.dto.DeviceTypeDetail"
            parameterType="java.lang.Integer">
@@ -99,6 +97,5 @@
          AND type IS NOT NULL
        GROUP BY type
    </select>
</mapper>
src/main/resources/mapper/device/DeviceMaintenanceMapper.xml
@@ -8,6 +8,7 @@
    <select id="queryPage" resultType="com.ruoyi.device.dto.DeviceMaintenanceDto">
        select dm.id,
        dm.device_ledger_id,
        dm.area_id,
        dm.maintenance_plan_time,
        dm.maintenance_actually_time,
        dm.maintenance_result,
@@ -20,9 +21,11 @@
        dm.maintenance_actually_name,
        dl.device_name,
        dl.device_model,
        da.area_name,
        su.nick_name as create_user_name
        from device_maintenance dm
        left join device_ledger dl on dm.device_ledger_id = dl.id
        left join device_area da on dm.area_id = da.id
        left join sys_user su on dm.create_user = su.user_id
        <where>
            1 = 1
@@ -53,6 +56,7 @@
    <select id="detailById" resultType="com.ruoyi.device.dto.DeviceMaintenanceDto">
        select dm.id,
               dm.device_ledger_id,
               dm.area_id,
               dm.maintenance_plan_time,
               dm.maintenance_actually_time,
               dm.maintenance_result,
@@ -65,9 +69,11 @@
               dm.maintenance_actually_name,
               dl.device_name,
               dl.device_model,
               da.area_name,
               su.user_name as create_user_name
        from device_maintenance dm
                 left join device_ledger dl on dm.device_ledger_id = dl.id
                 left join device_area da on dm.area_id = da.id
                 left join sys_user su on dm.create_user = su.user_id
        where dm.id = #{id}
    </select>
src/main/resources/mapper/device/DeviceRepairMapper.xml
@@ -8,6 +8,7 @@
    <select id="queryPage" resultType="com.ruoyi.device.dto.DeviceRepairDto">
        select dr.id,
               dr.device_ledger_id,
               dr.area_id,
                dr.repair_time,
                dr.repair_name,
                dr.remark,
@@ -21,9 +22,11 @@
                dr.update_user,
               dr.tenant_id,
               dl.device_name,
               dl.device_model
               dl.device_model,
               da.area_name
        from device_repair dr
        left join device_ledger dl on dr.device_ledger_id = dl.id
        left join device_area da on dr.area_id = da.id
        <where>
            1 = 1
            <if test="deviceRepairDto.deviceName != null">
@@ -53,6 +56,7 @@
    <select id="detailById" resultType="com.ruoyi.device.dto.DeviceRepairDto">
        select dr.id,
               dr.device_ledger_id,
               dr.area_id,
               dr.repair_time,
               dr.repair_name,
               dr.remark,
@@ -66,9 +70,11 @@
               dr.update_user,
               dr.tenant_id,
               dl.device_name,
               dl.device_model
               dl.device_model,
               da.area_name
        from device_repair dr
                 left join device_ledger dl on dr.device_ledger_id = dl.id
                 left join device_area da on dr.area_id = da.id
        where dr.id = #{id}
    </select>
src/main/resources/mapper/measuringinstrumentledger/MeasuringInstrumentLedgerMapper.xml
@@ -15,7 +15,7 @@
        next_date,
        record_date,
        CASE
        WHEN most_date &gt;=  DATE_FORMAT(now(),'%Y-%m-%d') THEN 1
        WHEN DATE_ADD(most_date, INTERVAL IFNULL(cycle, 0) DAY) &gt;= CURDATE() THEN 1
        ELSE 2
        END AS status,
        create_user,
@@ -40,10 +40,10 @@
            <if test="req.status != null">
                <choose>
                    <when test="req.status == 1">
                        AND most_date &gt;=  DATE_FORMAT(now(),'%Y-%m-%d')
                        AND DATE_ADD(most_date, INTERVAL IFNULL(cycle, 0) DAY) &gt;= CURDATE()
                    </when>
                    <when test="req.status == 2">
                        AND most_date &lt;  DATE_FORMAT(now(),'%Y-%m-%d')
                        AND DATE_ADD(most_date, INTERVAL IFNULL(cycle, 0) DAY) &lt; CURDATE()
                    </when>
                </choose>
            </if>