Bläddra i källkod

pms 生成保养工单逻辑功能优化

zhangcl 2 månader sedan
förälder
incheckning
815f718156
13 ändrade filer med 366 tillägg och 16 borttagningar
  1. 5 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmaintenancebom/vo/IotMaintenanceBomPageReqVO.java
  2. 17 1
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/IotMainWorkOrderController.java
  3. 3 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/vo/IotMainWorkOrderPageReqVO.java
  4. 8 2
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/vo/IotMainWorkOrderRespVO.java
  5. 3 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/vo/IotMainWorkOrderSaveReqVO.java
  6. 2 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorderbom/vo/IotMainWorkOrderBomPageReqVO.java
  7. 4 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/dataobject/iotmainworkorder/IotMainWorkOrderDO.java
  8. 2 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotmaintenancebom/IotMaintenanceBomMapper.java
  9. 1 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotmainworkorderbom/IotMainWorkOrderBomMapper.java
  10. 1 1
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/mainworkorder/CreateMainWorkOrderJob.java
  11. 95 9
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorder/IotMainWorkOrderServiceImpl.java
  12. 9 0
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorderbom/IotMainWorkOrderBomService.java
  13. 216 3
      yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorderbom/IotMainWorkOrderBomServiceImpl.java

+ 5 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmaintenancebom/vo/IotMaintenanceBomPageReqVO.java

@@ -9,6 +9,7 @@ import org.springframework.format.annotation.DateTimeFormat;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
@@ -112,4 +113,8 @@ public class IotMaintenanceBomPageReqVO extends PageParam {
     private String deviceStatus;
     @Schema(description = "资产性质")
     private String assetProperty;
+    @Schema(description = "设备id集合")
+    private List<Long> deviceIds;
+    @Schema(description = "保养项节点id集合")
+    private List<Long> bomNodeIds;
 }

+ 17 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/IotMainWorkOrderController.java

@@ -14,8 +14,10 @@ import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainW
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomPageReqVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorder.IotMainWorkOrderDO;
 import cn.iocoder.yudao.module.pms.service.iotmainworkorder.IotMainWorkOrderService;
+import cn.iocoder.yudao.module.pms.service.iotmainworkorderbom.IotMainWorkOrderBomService;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
@@ -32,6 +34,7 @@ import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@@ -48,6 +51,8 @@ public class IotMainWorkOrderController {
     private IotMainWorkOrderService iotMainWorkOrderService;
     @Resource
     private AdminUserApi adminUserApi;
+    @Resource
+    private IotMainWorkOrderBomService iotMainWorkOrderBomService;
 
     @PostMapping("/create")
     @Operation(summary = "创建保养工单")
@@ -137,14 +142,25 @@ public class IotMainWorkOrderController {
         if (CollUtil.isEmpty(orders)) {
             return Collections.emptyList();
         }
+        // 筛选出 计划生成 待执行 的工单 计算其距离保养时间
+        List<IotMainWorkOrderDO> distanceOrders = orders.stream()
+                .filter(order -> order.getType() == 1 && order.getResult() == 1)
+                .collect(Collectors.toList());
         // 查询保养计划 负责人
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(orders,
                 order -> Stream.of(NumberUtils.parseLong(order.getResponsiblePerson()))));
+        // 查询 各工单 明细保养项 的距离保养时间/里程/自然日期 包含多个保养项的 以最近到期时间为准
+        IotMainWorkOrderBomPageReqVO reqVO = new IotMainWorkOrderBomPageReqVO();
+        reqVO.setWorkOrderIds(convertListByFlatMap(distanceOrders, order -> Stream.of(order.getId())));
+        Map<Long, String> nearestDistances = iotMainWorkOrderBomService.mainWorkOrderNearestDistance(reqVO);
         // 2. 转换成 VO
         return BeanUtils.toBean(orders, IotMainWorkOrderRespVO.class, orderVO -> {
-            // 2.2 设置创建人、负责人名称
+            // 设置创建人、负责人名称
             MapUtils.findAndThen(userMap, NumberUtils.parseLong(orderVO.getResponsiblePerson()),
                     user -> orderVO.setResponsiblePersonName(user.getNickname()));
+            // 距离保养时间/公里数/自然日
+            MapUtils.findAndThen(nearestDistances, orderVO.getId(),
+                    distance -> orderVO.setMainDistance(distance));
         });
     }
 

+ 3 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/vo/IotMainWorkOrderPageReqVO.java

@@ -27,6 +27,9 @@ public class IotMainWorkOrderPageReqVO extends PageParam {
     @Schema(description = "组织id", example = "8684")
     private Long deptId;
 
+    @Schema(description = "分组工单id  多个责任人操作相同设备内容的工单 关联关系 uuid")
+    private String orderGroupId;
+
     @Schema(description = "工单号")
     private String orderNumber;
 

+ 8 - 2
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/vo/IotMainWorkOrderRespVO.java

@@ -31,6 +31,9 @@ public class IotMainWorkOrderRespVO {
     @ExcelProperty("组织id")
     private Long deptId;
 
+    @Schema(description = "分组工单id  多个责任人操作相同设备内容的工单 关联关系 uuid")
+    private String orderGroupId;
+
     @Schema(description = "工单号")
     @ExcelProperty("工单号")
     private String orderNumber;
@@ -102,8 +105,11 @@ public class IotMainWorkOrderRespVO {
     /**
      * 扩展字段
      */
-    @Schema(description = "距离保养时间")
-    private BigDecimal remainingTime;
+    @Schema(description = "距离保养时间 单位可能是 H D KM")
+    private String mainDistance;
+
+    @Schema(description = "距离保养时间 单位 H D KM")
+    private String mainDistanceUnit;
 
     @Schema(description = "筛选了指定设备的保养工单bom")
     private List<IotMainWorkOrderBomRespVO> workOrderBomS;

+ 3 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorder/vo/IotMainWorkOrderSaveReqVO.java

@@ -22,6 +22,9 @@ public class IotMainWorkOrderSaveReqVO {
     @Schema(description = "组织id", example = "8684")
     private Long deptId;
 
+    @Schema(description = "分组工单id  多个责任人操作相同设备内容的工单 关联关系 uuid")
+    private String orderGroupId;
+
     @Schema(description = "工单号")
     private String orderNumber;
 

+ 2 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/controller/admin/iotmainworkorderbom/vo/IotMainWorkOrderBomPageReqVO.java

@@ -153,4 +153,6 @@ public class IotMainWorkOrderBomPageReqVO extends PageParam {
     private List<Long> deviceIds;
     @Schema(description = "设备bom节点id列表")
     private List<Long> bomNodeIds;
+    @Schema(description = "保养工单id集合")
+    private List<Long> workOrderIds;
 }

+ 4 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/dataobject/iotmainworkorder/IotMainWorkOrderDO.java

@@ -41,6 +41,10 @@ public class IotMainWorkOrderDO extends BaseDO {
      * 组织id
      */
     private Long deptId;
+    /**
+     * 分组工单id  多个责任人操作相同设备内容的工单 关联关系 uuid
+     */
+    private String orderGroupId;
     /**
      * 工单号
      */

+ 2 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotmaintenancebom/IotMaintenanceBomMapper.java

@@ -63,6 +63,7 @@ public interface IotMaintenanceBomMapper extends BaseMapperX<IotMaintenanceBomDO
                 .eqIfPresent(IotMaintenanceBomDO::getPlanDetailId, reqVO.getPlanDetailId())
                 .eqIfPresent(IotMaintenanceBomDO::getDeviceCategoryId, reqVO.getDeviceCategoryId())
                 .eqIfPresent(IotMaintenanceBomDO::getDeviceId, reqVO.getDeviceId())
+                .inIfPresent(IotMaintenanceBomDO::getDeviceId, reqVO.getDeviceIds())
                 .eqIfPresent(IotMaintenanceBomDO::getRule, reqVO.getRule())
                 .betweenIfPresent(IotMaintenanceBomDO::getLastRunningTime, reqVO.getLastRunningTime())
                 .betweenIfPresent(IotMaintenanceBomDO::getNextRunningTime, reqVO.getNextRunningTime())
@@ -70,6 +71,7 @@ public interface IotMaintenanceBomMapper extends BaseMapperX<IotMaintenanceBomDO
                 .eqIfPresent(IotMaintenanceBomDO::getNextRunningKilometers, reqVO.getNextRunningKilometers())
                 .betweenIfPresent(IotMaintenanceBomDO::getLastNaturalDate, reqVO.getLastNaturalDate())
                 .eqIfPresent(IotMaintenanceBomDO::getNextNaturalDate, reqVO.getNextNaturalDate())
+                .inIfPresent(IotMaintenanceBomDO::getBomNodeId, reqVO.getBomNodeIds())
                 .likeIfPresent(IotMaintenanceBomDO::getName, reqVO.getName())
                 .eqIfPresent(IotMaintenanceBomDO::getCode, reqVO.getCode())
                 .eqIfPresent(IotMaintenanceBomDO::getParentId, reqVO.getParentId())

+ 1 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/dal/mysql/iotmainworkorderbom/IotMainWorkOrderBomMapper.java

@@ -67,6 +67,7 @@ public interface IotMainWorkOrderBomMapper extends BaseMapperX<IotMainWorkOrderB
                 .eqIfPresent(IotMainWorkOrderBomDO::getPlanId, reqVO.getPlanId())
                 .eqIfPresent(IotMainWorkOrderBomDO::getPlanDetailId, reqVO.getPlanDetailId())
                 .eqIfPresent(IotMainWorkOrderBomDO::getWorkOrderId, reqVO.getWorkOrderId())
+                .inIfPresent(IotMainWorkOrderBomDO::getWorkOrderId, reqVO.getWorkOrderIds())
                 .eqIfPresent(IotMainWorkOrderBomDO::getDeviceCategoryId, reqVO.getDeviceCategoryId())
                 .eqIfPresent(IotMainWorkOrderBomDO::getDeviceId, reqVO.getDeviceId())
                 .inIfPresent(IotMainWorkOrderBomDO::getDeviceId, reqVO.getDeviceIds())

+ 1 - 1
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/job/mainworkorder/CreateMainWorkOrderJob.java

@@ -122,7 +122,7 @@ public class CreateMainWorkOrderJob implements JobHandler {
                     LocalDateTime lastNaturalDate = bom.getLastNaturalDate();      // 上次保养自然日期
                     BigDecimal naturalDatePeriod = bom.getNextNaturalDate();        // 自然日周期
                     BigDecimal natualDateLead = bom.getNaturalDatePeriodLead();    // 自然日周期提前量
-                    if (ObjUtil.isNotEmpty(lastNaturalDate) && ObjUtil.isNotEmpty(lastNaturalDate) && ObjUtil.isNotEmpty(lastNaturalDate)) {
+                    if (ObjUtil.isNotEmpty(lastNaturalDate) && ObjUtil.isNotEmpty(naturalDatePeriod) && ObjUtil.isNotEmpty(natualDateLead)) {
                         // 计算有效天数 = 自然日周期 - 提前量
                         long days = naturalDatePeriod.subtract(natualDateLead).longValue(); // 转为长整型天数
                         // 计算目标日期:上次保养日期 + 有效天数(注意:LocalDateTime加天数会自动处理日期进位)

+ 95 - 9
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorder/IotMainWorkOrderServiceImpl.java

@@ -7,7 +7,9 @@ import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.iotdevicerunlog.vo.IotDeviceRunLogRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotlockstock.vo.IotLockStockPageReqVO;
+import cn.iocoder.yudao.module.pms.controller.admin.iotmaintenancebom.vo.IotMaintenanceBomPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorder.vo.IotMainWorkOrderSaveReqVO;
@@ -18,24 +20,31 @@ import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMa
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbommaterial.vo.IotMainWorkOrderBomMaterialSaveReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.vo.IotDeviceRespVO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotlockstock.IotLockStockDO;
+import cn.iocoder.yudao.module.pms.dal.dataobject.iotmaintenancebom.IotMaintenanceBomDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorder.IotMainWorkOrderDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorderbom.IotMainWorkOrderBomDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorderbommaterial.IotMainWorkOrderBomMaterialDO;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotlockstock.IotLockStockMapper;
+import cn.iocoder.yudao.module.pms.dal.mysql.iotmaintenancebom.IotMaintenanceBomMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotmainworkorder.IotMainWorkOrderMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotmainworkorderbom.IotMainWorkOrderBomMapper;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotmainworkorderbommaterial.IotMainWorkOrderBomMaterialMapper;
 import cn.iocoder.yudao.module.pms.dal.redis.BizNoRedisDAO;
 import cn.iocoder.yudao.module.pms.service.IotDeviceService;
+import cn.iocoder.yudao.module.pms.service.iotdevicerunlog.IotDeviceRunLogService;
+import cn.iocoder.yudao.module.pms.service.iotmaintenancebom.IotMaintenanceBomService;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
 import java.util.*;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -63,6 +72,12 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
     private IotDeviceService iotDeviceService;
     @Resource
     private IotLockStockMapper iotLockStockMapper;
+    @Autowired
+    private IotDeviceRunLogService iotDeviceRunLogService;
+    @Autowired
+    private IotMaintenanceBomService iotMaintenanceBomService;
+    @Autowired
+    private IotMaintenanceBomMapper iotMaintenanceBomMapper;
 
     @Override
     public Long createIotMainWorkOrder(IotMainWorkOrderSaveReqVO createReqVO) {
@@ -139,21 +154,17 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
         if (ObjectUtil.isEmpty(workOrderBOMs)) {
             throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
         }
+        // 不是必填项:推迟保养;委外保养
         List<IotMainWorkOrderBomMaterialSaveReqVO> workOrderMaterials = saveVO.getMainWorkOrderMaterials();
-        if (ObjectUtil.isEmpty(workOrderMaterials)) {
+        /* if (ObjectUtil.isEmpty(workOrderMaterials)) {
             throw exception(IOT_MAIN_WORK_ORDER_NOT_EXISTS);
-        }
+        } */
         // 保养工单 主表 还有 保养费用 、人工费用 、其他费用
         IotMainWorkOrderDO updateObj = BeanUtils.toBean(mainWorkOrder, IotMainWorkOrderDO.class);
-        updateObj.setResult(2);
-        iotMainWorkOrderMapper.updateById(updateObj);
+
         // 保养工单明细
         List<IotMainWorkOrderBomDO> workOrderBomDOS = new ArrayList<>();
-        workOrderBOMs.forEach(bom -> {
-            IotMainWorkOrderBomDO tempBom = BeanUtils.toBean(bom, IotMainWorkOrderBomDO.class);
-            workOrderBomDOS.add(tempBom);
-        });
-        iotMainWorkOrderBomMapper.updateBatch(workOrderBomDOS);
+
         // 保养工单 bom 明细关联的物料消耗
         List<IotMainWorkOrderBomMaterialDO> workOrderBomMaterialDOS = new ArrayList<>();
         Set<Long> factoryIds = new HashSet<>();
@@ -161,10 +172,16 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
         Set<Long> storageLocationIds = new HashSet<>();
         Set<String> materialCodes = new HashSet<>();
         Map<String, BigDecimal> lockStockPair = new HashMap<>();
+        // 标识保养项是否已经添加了物料
+        Set<Long> bomNodeIds = new HashSet<>();
+        AtomicBoolean mainCompleted = new AtomicBoolean(true);
+        // 已经完成保养的保养项关联的 设备id 集合
+        Set<Long> deviceIds = new HashSet<>();
         workOrderMaterials.forEach(material -> {
             IotMainWorkOrderBomMaterialDO tempMaterial = BeanUtils.toBean(material, IotMainWorkOrderBomMaterialDO.class);
             tempMaterial.setWorkOrderId(mainWorkOrder.getId());
             workOrderBomMaterialDOS.add(tempMaterial);
+
             if ("本地库存".equals(material.getMaterialSource())) {
                 if (ObjUtil.isNotEmpty(material.getFactoryId())) {
                     factoryIds.add(material.getFactoryId());
@@ -191,6 +208,75 @@ public class IotMainWorkOrderServiceImpl implements IotMainWorkOrderService {
         });
         // 添加 工单消耗的物料记录
         iotMainWorkOrderBomMaterialMapper.insertBatch(workOrderBomMaterialDOS);
+        workOrderBOMs.forEach(bom -> {
+            IotMainWorkOrderBomDO tempBom = BeanUtils.toBean(bom, IotMainWorkOrderBomDO.class);
+            // 如果工单明细中任何一个保养项设置了 任何一种推迟保养规则 工单结果 不能设置为 已执行
+            if (bom.getDelayKilometers().compareTo(BigDecimal.ZERO)>0
+                    || bom.getDelayDuration().compareTo(BigDecimal.ZERO)>0 || bom.getDelayNaturalDate().compareTo(BigDecimal.ZERO)>0) {
+                tempBom.setStatus(0);
+                mainCompleted.set(false);
+            }
+            if (bom.getDelayKilometers().compareTo(BigDecimal.ZERO)==0
+                    || bom.getDelayDuration().compareTo(BigDecimal.ZERO)==0 || bom.getDelayNaturalDate().compareTo(BigDecimal.ZERO)==0) {
+                // 如果保养项没有设置延时保养 则查询当前设备的 累计运行公里数 累计运行时间 当前时间日期 赋值到 关联保养计划 明细 的 对应保养规则数据上
+                deviceIds.add(bom.getDeviceId());
+                // 保养项下如果已经添加了物料 说明该保养项已经保养完成
+                bomNodeIds.add(bom.getBomNodeId());
+                tempBom.setStatus(1);
+            }
+            workOrderBomDOS.add(tempBom);
+        });
+        iotMainWorkOrderBomMapper.updateBatch(workOrderBomDOS);
+        // 只有工单明细中所有保养项的物料都选择完成,才会将工单结果设置为 ‘已执行’
+        if (mainCompleted.get()) {
+            updateObj.setResult(2);
+        }
+        iotMainWorkOrderMapper.updateById(updateObj);
+        // 如果有保养项已经保养完成,根据保养项规则 实时更新 关联 的 保养计划 明细 保养项的 ‘上次保养运行时间、上次保养自然日期、上次保养公里数’
+        // 查询明细中所有设备的累计运行里程、累计运行时间
+        if (CollUtil.isNotEmpty(deviceIds)) {
+            Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMap(new ArrayList<>(deviceIds));
+            // 查询保养工单关联的保养计划 明细
+            IotMaintenanceBomPageReqVO reqVO = new IotMaintenanceBomPageReqVO();
+            reqVO.setPlanId(updateObj.getPlanId());
+            reqVO.setDeviceIds(new ArrayList<>(deviceIds));
+            reqVO.setBomNodeIds(new ArrayList<>(bomNodeIds));
+            List<IotMaintenanceBomDO> maintenanceBomS = iotMaintenanceBomService.getIotMainPlanBomList(reqVO);
+            // 根据保养工单 保养项 保养数据 更新对应的 保养计划 保养项 数据
+            if (CollUtil.isNotEmpty(maintenanceBomS)) {
+                List<IotMaintenanceBomDO> tobeUpdatedMainBomS = new ArrayList<>();
+                maintenanceBomS.forEach(bom -> {
+                    if (0 == bom.getMileageRule()) {
+                        // 设置了 运行里程 保养规则
+                        if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
+                            IotDeviceRunLogRespVO deviceRunLog = deviceRunLogMap.get(bom.getDeviceId());
+                            BigDecimal totalMileage = deviceRunLog.getTotalMileage();
+                            if (ObjUtil.isNotEmpty(totalMileage) && totalMileage.compareTo(BigDecimal.ZERO)>0) {
+                                bom.setLastRunningKilometers(totalMileage);
+                            }
+                        }
+                    }
+                    if (0 == bom.getRunningTimeRule()) {
+                        // 设置了 运行时间 保养规则
+                        if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
+                            IotDeviceRunLogRespVO deviceRunLog = deviceRunLogMap.get(bom.getDeviceId());
+                            BigDecimal totalRunTime = deviceRunLog.getTotalRunTime();
+                            if (ObjUtil.isNotEmpty(totalRunTime) && totalRunTime.compareTo(BigDecimal.ZERO)>0) {
+                                bom.setLastRunningTime(totalRunTime);
+                            }
+                        }
+                    }
+                    if (0 == bom.getNaturalDateRule()) {
+                        // 设置了 自然日期 保养规则
+                        bom.setLastNaturalDate(LocalDateTime.now());
+                    }
+                    tobeUpdatedMainBomS.add(bom);
+                });
+                if (CollUtil.isNotEmpty(tobeUpdatedMainBomS)) {
+                    iotMaintenanceBomMapper.updateBatch(tobeUpdatedMainBomS);
+                }
+            }
+        }
         // 只扣减本地库存 不处理SAP库存
         if (CollUtil.isNotEmpty(factoryIds) && CollUtil.isNotEmpty(costCenterIds) && CollUtil.isNotEmpty(materialCodes)) {
             IotLockStockPageReqVO reqVO = new IotLockStockPageReqVO();

+ 9 - 0
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorderbom/IotMainWorkOrderBomService.java

@@ -7,6 +7,7 @@ import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorderbom.IotMainWor
 
 import javax.validation.Valid;
 import java.util.List;
+import java.util.Map;
 
 /**
  * PMS 保养计划明细BOM Service 接口
@@ -61,6 +62,14 @@ public interface IotMainWorkOrderBomService {
      */
     List<IotMainWorkOrderBomDO> getIotMainWorkOrderBomList(IotMainWorkOrderBomPageReqVO pageReqVO);
 
+    /**
+     * 根据保养工单id集合查询保养工单明细最近保养时间
+     *
+     * @param pageReqVO 分页查询
+     * @return PMS 保养计划明细BOM分页
+     */
+    Map<Long, String> mainWorkOrderNearestDistance(IotMainWorkOrderBomPageReqVO pageReqVO);
+
     /**
      * 批量 新增保养工单 明细BOM
      *

+ 216 - 3
yudao-module-pms/yudao-module-pms-biz/src/main/java/cn/iocoder/yudao/module/pms/service/iotmainworkorderbom/IotMainWorkOrderBomServiceImpl.java

@@ -1,19 +1,25 @@
 package cn.iocoder.yudao.module.pms.service.iotmainworkorderbom;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.pms.controller.admin.iotdevicerunlog.vo.IotDeviceRunLogRespVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomPageReqVO;
 import cn.iocoder.yudao.module.pms.controller.admin.iotmainworkorderbom.vo.IotMainWorkOrderBomSaveReqVO;
-import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorder.IotMainWorkOrderDO;
 import cn.iocoder.yudao.module.pms.dal.dataobject.iotmainworkorderbom.IotMainWorkOrderBomDO;
 import cn.iocoder.yudao.module.pms.dal.mysql.iotmainworkorderbom.IotMainWorkOrderBomMapper;
 import cn.iocoder.yudao.module.pms.enums.ErrorCodeConstant;
+import cn.iocoder.yudao.module.pms.service.iotdevicerunlog.IotDeviceRunLogService;
+import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
-
-import java.util.List;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 
@@ -28,6 +34,8 @@ public class IotMainWorkOrderBomServiceImpl implements IotMainWorkOrderBomServic
 
     @Resource
     private IotMainWorkOrderBomMapper iotMainWorkOrderBomMapper;
+    @Autowired
+    private IotDeviceRunLogService iotDeviceRunLogService;
 
     @Override
     public Long createIotMainWorkOrderBom(IotMainWorkOrderBomSaveReqVO createReqVO) {
@@ -76,6 +84,211 @@ public class IotMainWorkOrderBomServiceImpl implements IotMainWorkOrderBomServic
         return iotMainWorkOrderBomMapper.selectList(pageReqVO);
     }
 
+    @Override
+    public Map<Long, String> mainWorkOrderNearestDistance(IotMainWorkOrderBomPageReqVO pageReqVO) {
+        List<IotMainWorkOrderBomDO> workOrderBomS = getIotMainWorkOrderBomList(pageReqVO);
+        Map<Long, String> resultMap = new HashMap<>();
+        // 遍历工单明细 找出每个工单中 触发保养时间最近的 时间/里程/自然日
+        if (CollUtil.isNotEmpty(workOrderBomS)) {
+            Set<Long> deviceIds = new HashSet<>();
+            workOrderBomS.forEach(bom -> {
+                deviceIds.add(bom.getDeviceId());
+            });
+            // 查询当前所有设备的 累计运行里程 累计运行时间
+            Map<Long, IotDeviceRunLogRespVO> deviceRunLogMap = iotDeviceRunLogService.getDeviceRunLogMap(new ArrayList<>(deviceIds));
+            Map<Long, List<Map<String, Object>>> orderDistancePair = new HashMap<>();
+            workOrderBomS.forEach(bom -> {
+                BigDecimal runningTimeDistance = null;
+                BigDecimal runningKiloDistance = null;
+                BigDecimal naturalDateDistance = null;
+                // 计算每个保养项 每个保养规则下的 距离保养时间
+                if (0 == bom.getRunningTimeRule()) {
+                    // 运行时间保养规则
+                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
+                        BigDecimal totalRunTime = deviceRunLogMap.get(bom.getDeviceId()).getTotalRunTime();
+                        BigDecimal lastRunningTime = bom.getLastRunningTime();      // 上次保养运行时间
+                        BigDecimal runningTimePeriod = bom.getNextRunningTime();    // 运行时间周期
+                        BigDecimal timePeriodLead = bom.getTimePeriodLead();    // 运行时间周期提前量
+                        runningTimeDistance = (lastRunningTime.add(runningTimePeriod).subtract(timePeriodLead)).subtract(totalRunTime);
+                    }
+                }
+                if (0 == bom.getMileageRule()) {
+                    // 运行里程保养规则
+                    // 累计运行里程规则 累计运行里程 >= (上次保养运行里程+运行里程周期-提前量)
+                    if (deviceRunLogMap.containsKey(bom.getDeviceId())) {
+                        BigDecimal totalMileage = deviceRunLogMap.get(bom.getDeviceId()).getTotalMileage();
+                        BigDecimal lastRunningKilo = bom.getLastRunningKilometers();      // 上次保养运行里程
+                        BigDecimal runningKiloPeriod = bom.getNextRunningKilometers();    // 运行里程周期
+                        BigDecimal kiloCycleLead = bom.getKiloCycleLead();    // 运行里程周期提前量
+                        runningKiloDistance = (lastRunningKilo.add(runningKiloPeriod).subtract(kiloCycleLead)).subtract(totalMileage);
+                    }
+                }
+                if (0 == bom.getNaturalDateRule()) {
+                    // 自然日期保养规则
+                    LocalDateTime lastNaturalDate = bom.getLastNaturalDate();      // 上次保养自然日期
+                    BigDecimal naturalDatePeriod = bom.getNextNaturalDate();        // 自然日周期
+                    BigDecimal natualDateLead = bom.getNaturalDatePeriodLead();    // 自然日周期提前量
+                    if (ObjUtil.isNotEmpty(lastNaturalDate) && ObjUtil.isNotEmpty(naturalDatePeriod) && ObjUtil.isNotEmpty(natualDateLead)) {
+                        // 计算有效天数 = 自然日周期 - 提前量
+                        long days = naturalDatePeriod.subtract(natualDateLead).longValue(); // 转为长整型天数
+                        // 计算目标日期:上次保养日期 + 有效天数(注意:LocalDateTime加天数会自动处理日期进位)
+                        LocalDateTime targetDate = lastNaturalDate.plusDays(days);
+                        // 获取当前日期时间
+                        LocalDateTime now = LocalDateTime.now();
+                        // 计算日期差值(以天为单位)
+                        naturalDateDistance = new BigDecimal(ChronoUnit.DAYS.between(targetDate, now));
+                    }
+                }
+                // 找出距离0最近的数,作为工单的最近保养距离数据
+                Object closet = chooseNearestDistance(runningTimeDistance, runningKiloDistance, naturalDateDistance);
+                Map<String, Object> tempDistance = new HashMap<>();
+                if (closet == runningTimeDistance) {
+                    tempDistance.put("H", closet);
+                } else if (closet == runningKiloDistance) {
+                    tempDistance.put("KM", closet);
+                } else if (closet == naturalDateDistance) {
+                    tempDistance.put("D", closet);
+                }
+                if (orderDistancePair.containsKey(bom.getWorkOrderId())) {
+                    List<Map<String, Object>> tempDistances = orderDistancePair.get(bom.getWorkOrderId());
+                    tempDistances.add(tempDistance);
+                    orderDistancePair.put(bom.getWorkOrderId(), tempDistances);
+                } else {
+                    List<Map<String, Object>> tempDistances = new ArrayList<>();
+                    tempDistances.add(tempDistance);
+                    orderDistancePair.put(bom.getWorkOrderId(), tempDistances);
+                }
+            });
+            // 以保养工单id 为维度 统计每个保养工单明细中 距离最近的保养数据
+            resultMap = findClosestToZero(orderDistancePair);
+        }
+        return resultMap;
+    }
+
+    /**
+     * 找出每个工单对应的集合中 距离时间最短的 数据
+     * @param orderDistancePair
+     */
+    private Map<Long, String> findClosestToZero(Map<Long, List<Map<String, Object>>> orderDistancePair){
+        Map<Long, String> result = new HashMap<>();
+        for (Map.Entry<Long, List<Map<String, Object>>> entry : orderDistancePair.entrySet()) {
+            Long key = entry.getKey();
+            List<Map<String, Object>> mapList = entry.getValue();
+
+            // 初始化最小距离和对应的Map
+            BigDecimal minAbsDistance = null;
+            Map<String, Object> closestMap = null;
+
+            for (Map<String, Object> currentMap : mapList) {
+                // 计算当前Map所有值的最小绝对值
+                BigDecimal currentMinAbs = calculateMinAbsolute(currentMap);
+
+                // 跳过无效Map(无有效值)
+                if (currentMinAbs == null) continue;
+
+                // 比较并更新最小值
+                if (minAbsDistance == null || currentMinAbs.compareTo(minAbsDistance) < 0) {
+                    minAbsDistance = currentMinAbs;
+                    closestMap = currentMap;
+                }
+            }
+            // 将有效结果放入最终Map
+            if (closestMap != null) {
+                result.put(key, convertMapToString(closestMap));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * 将Map转换为String格式:value1 key1; value2 key2
+     */
+    private String convertMapToString(Map<String, Object> map) {
+        StringBuilder sb = new StringBuilder();
+        for (Map.Entry<String, Object> entry : map.entrySet()) {
+            if (sb.length() > 0) {
+                sb.append(";");  // 添加分隔符
+            }
+            String key = entry.getKey();
+            Object value = entry.getValue();
+
+            // 处理null值
+            String valueStr = (value != null) ? value.toString() : "null";
+
+            // 转义特殊字符(如有需要)
+            sb.append(valueStr).append(" ").append(key);
+        }
+        return sb.toString();
+    }
+
+    /**
+     * 计算单个Map中所有BigDecimal值的最小绝对值
+     * @param map
+     * @return
+     */
+    private BigDecimal calculateMinAbsolute(Map<String, Object> map) {
+        BigDecimal minAbs = null;
+
+        for (Object value : map.values()) {
+            if (!(value instanceof BigDecimal)) continue;
+
+            BigDecimal decimalValue = (BigDecimal) value;
+            BigDecimal absValue = decimalValue.abs();
+
+            if (minAbs == null || absValue.compareTo(minAbs) < 0) {
+                minAbs = absValue;
+            }
+        }
+        return minAbs; // 可能为null(无BigDecimal值)
+    }
+
+    private BigDecimal chooseNearestDistance(BigDecimal runningTimeDistance, BigDecimal runningKiloDistance, BigDecimal naturalDateDistance){
+        // 存储当前找到的最小绝对值的变量
+        BigDecimal closest = null;
+        // 存储当前最小绝对值(BigDecimal 类型)
+        BigDecimal minAbs = null;
+        // 比较第一个变量
+        if (runningTimeDistance != null) {
+            closest = runningTimeDistance;
+            minAbs = runningTimeDistance.abs();
+        }
+        // 比较第二个变量
+        if (runningKiloDistance != null) {
+            if (minAbs == null) {
+                // 如果之前没有有效值,直接使用 b
+                closest = runningKiloDistance;
+                minAbs = runningKiloDistance.abs();
+            } else {
+                // 比较绝对值大小
+                BigDecimal absB = runningKiloDistance.abs();
+                int cmp = absB.compareTo(minAbs);
+                if (cmp < 0 || (cmp == 0 && runningKiloDistance.compareTo(closest) > 0)) {
+                    // 如果 b 的绝对值更小,或绝对值相等但 b 是正数(更大值表示正数)
+                    closest = runningKiloDistance;
+                    minAbs = absB;
+                }
+            }
+        }
+        // 比较第三个变量
+        if (naturalDateDistance != null) {
+            if (minAbs == null) {
+                // 如果之前没有有效值,直接使用 c
+                closest = naturalDateDistance;
+                minAbs = naturalDateDistance.abs();
+            } else {
+                // 比较绝对值大小
+                BigDecimal absC = naturalDateDistance.abs();
+                int cmp = absC.compareTo(minAbs);
+                if (cmp < 0 || (cmp == 0 && naturalDateDistance.compareTo(closest) > 0)) {
+                    // 如果 c 的绝对值更小,或绝对值相等但 c 是正数
+                    closest = naturalDateDistance;
+                    minAbs = absC;
+                }
+            }
+        }
+        return closest; // 可能返回 null(当所有输入均为 null 时)
+    }
+
     @Override
     public void batchAddOrderBOMs(List<IotMainWorkOrderBomDO> workOrderBOMs) {
         iotMainWorkOrderBomMapper.insertBatch(workOrderBOMs);