From bf5f52656e128f578ed42c7979c31cf4b476ce46 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期六, 31 十月 2026 19:04:40 +0800
Subject: [PATCH] feat: 优化业务导入及工艺流程管理

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java |  223 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 214 insertions(+), 9 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
index d19426b..b723f98 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java
@@ -2,22 +2,32 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.module.mes.service.pd.processparamtemplate.MesPdProcessParamTemplateService;
+import cn.iocoder.yudao.module.mes.service.pro.feedback.dto.MesTaskParamSnapshot;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskAutoGeneratePreviewRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskAutoGenerateReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.machinery.MesDvMachineryDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationMachineDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
 import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum;
+import cn.iocoder.yudao.module.mes.service.dv.machinery.MesDvMachineryService;
+import cn.iocoder.yudao.module.mes.service.cal.team.MesCalTeamService;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamDO;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
 import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
+import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationMachineService;
 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
 import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
@@ -39,18 +49,30 @@
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_ALREADY_FINISHED;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_CANNOT_PAUSE;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_HAS_FEEDBACK;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_TASK_NOT_PAUSED;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskPauseRecordDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.task.MesProTaskPauseRecordMapper;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskPauseReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.task.vo.MesProTaskResumeReqVO;
 
 /**
  * MES 鐢熶骇浠诲姟 Service 瀹炵幇绫�
  *
- * @author 鑺嬮亾婧愮爜
+ * @author 瓒呯骇绠$悊鍛�
  */
 @Service
 @Validated
@@ -58,9 +80,14 @@
 
     @Resource
     private MesProTaskMapper taskMapper;
+    @Resource
+    private MesPdProcessParamTemplateService paramTemplateService;
 
     @Resource
     private MesProFeedbackMapper feedbackMapper;
+
+    @Resource
+    private MesProTaskPauseRecordMapper taskPauseRecordMapper;
 
     @Resource
     @Lazy
@@ -86,25 +113,30 @@
     private MesProWorkOrderProcessService workOrderProcessService;
     @Resource
     private MesMdAutoCodeRecordService autoCodeRecordService;
+    @Resource
+    private MesMdWorkstationMachineService workstationMachineService;
+    @Resource
+    @Lazy
+    private MesDvMachineryService machineryService;
+    @Resource
+    private MesCalTeamService teamService;
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createTask(MesProTaskSaveReqVO createReqVO) {
         // 1. 鏍¢獙鍏宠仈鏁版嵁瀛樺湪
         MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId());
         workstationService.validateWorkstationExists(createReqVO.getWorkstationId());
         routeService.validateRouteExists(createReqVO.getRouteId());
         processService.validateProcessExistsAndEnable(createReqVO.getProcessId());
+        fillTeamInfo(createReqVO);
 
         // 2. 鑷姩纭畾 itemId锛氫紭鍏堝墠绔紶鍏� 鈫� 宸ヨ壓璺嚎宸ュ簭鐨勪骇鍑轰骇鍝� 鈫� 宸ュ崟鎴愬搧
         Long itemId = createReqVO.getItemId();
         if (itemId == null) {
             MesProRouteProcessDO routeProcess = routeProcessService
                     .getRouteProcessByRouteIdAndProcessId(createReqVO.getRouteId(), createReqVO.getProcessId());
-            if (routeProcess != null && routeProcess.getOutputItemId() != null) {
-                itemId = routeProcess.getOutputItemId();
-            } else {
-                itemId = workOrder.getProductId();
-            }
+            itemId = resolveTaskItemId(routeProcess, workOrder.getProductId());
         }
         MesMdItemDO item = itemService.validateItemExists(itemId);
 
@@ -118,14 +150,17 @@
             task.setClientId(workOrder.getClientId());
         }
         // 2.2 鎻掑叆
+        freezeTaskParamConfig(task);
         taskMapper.insert(task);
         return task.getId();
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateTask(MesProTaskSaveReqVO updateReqVO) {
-        // 1.1 鏍¢獙瀛樺湪
-        MesProTaskDO task = validateTaskExists(updateReqVO.getId());
+        MesProTaskDO task = taskMapper.selectOne(new LambdaQueryWrapperX<MesProTaskDO>()
+                .eq(MesProTaskDO::getId, updateReqVO.getId()).last("FOR UPDATE"));
+        if (task == null) throw exception(PRO_TASK_NOT_EXISTS);
         // 1.2 鏍¢獙鍏宠仈鏁版嵁瀛樺湪
         if (updateReqVO.getWorkOrderId() != null) {
             workOrderService.validateWorkOrderExists(updateReqVO.getWorkOrderId());
@@ -139,6 +174,7 @@
         if (updateReqVO.getProcessId() != null) {
             processService.validateProcessExistsAndEnable(updateReqVO.getProcessId());
         }
+        fillTeamInfo(updateReqVO);
         MesMdItemDO item;
         if (updateReqVO.getItemId() != null) {
             item = itemService.validateItemExists(updateReqVO.getItemId());
@@ -150,7 +186,53 @@
         // 2. 鏇存柊
         MesProTaskDO updateObj = BeanUtils.toBean(updateReqVO, MesProTaskDO.class)
                 .setName(buildTaskName(item, quantity));
+        if (updateReqVO.getProcessId() != null && !updateReqVO.getProcessId().equals(task.getProcessId())) {
+            if (feedbackMapper.selectCountByTaskId(task.getId()) > 0) throw exception(PRO_TASK_HAS_FEEDBACK);
+            freezeTaskParamConfig(updateObj);
+        }
         taskMapper.updateById(updateObj);
+    }
+
+    /** 浠庡伐搴忓拰妯℃澘澶嶅埗瀹氫箟锛屽悗缁ā鏉跨淮鎶や笉鏀瑰彉宸叉帓浜т换鍔°�� */
+    private void freezeTaskParamConfig(MesProTaskDO task) {
+        var process = processService.getProcess(task.getProcessId());
+        if (process == null) throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_PROCESS_NOT_EXISTS);
+        int mode = process.getParamCollectMode() == null ? 0 : process.getParamCollectMode();
+        task.setParamCollectMode(mode);
+        task.setParamTemplateSnapshot("");
+        task.setParamSharedSnapshot("");
+        if (mode == 0) return;
+        if ((mode != 1 && mode != 2) || process.getParamTemplateId() == null)
+            throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_PROCESS_PARAM_CONFIG_INVALID);
+        var template = paramTemplateService.validateProcessParamTemplateExists(process.getParamTemplateId());
+        var details = paramTemplateService.getProcessParamTemplateDetailListByTemplateId(template.getId());
+        if (CollUtil.isEmpty(details)) throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_WORK_ORDER_PARAM_RECORD_TEMPLATE_NO_DETAIL);
+        MesTaskParamSnapshot snapshot = new MesTaskParamSnapshot();
+        snapshot.setTemplateId(template.getId());
+        snapshot.setTemplateCode(template.getTemplateCode());
+        snapshot.setTemplateName(template.getTemplateName());
+        List<MesTaskParamSnapshot.Detail> frozenDetails = BeanUtils.toBean(details, MesTaskParamSnapshot.Detail.class);
+        for (MesTaskParamSnapshot.Detail detail : frozenDetails) {
+            if (detail.getUnitMeasureId() != null) {
+                var unit = unitMeasureService.getUnitMeasure(detail.getUnitMeasureId());
+                if (unit != null) detail.setUnitMeasureName(unit.getName());
+            }
+        }
+        snapshot.setDetails(frozenDetails);
+        task.setParamTemplateSnapshot(JsonUtils.toJsonString(snapshot));
+    }
+
+    /**
+     * 鏍¢獙鐝粍瀛樺湪骞跺洖濉彮缁勫悕绉�
+     */
+    private void fillTeamInfo(MesProTaskSaveReqVO reqVO) {
+        if (reqVO.getTeamId() == null) {
+            return;
+        }
+        MesCalTeamDO team = teamService.validateTeamExists(reqVO.getTeamId());
+        if (team != null) {
+            reqVO.setTeamName(team.getName());
+        }
     }
 
     /**
@@ -215,6 +297,30 @@
         return routeProcesses;
     }
 
+    /**
+     * 浠诲姟浜у嚭鐗╂枡锛氫紭鍏堝彇宸ュ簭鍦ㄥ綋鍓嶄骇鍝佷笅鐨勪骇鍑猴紙鎸夊綊灞炶繃婊わ級锛屾病鏈変骇鍑烘椂鍙栧伐鍗曟垚鍝�
+     */
+    private Long resolveTaskItemId(MesProRouteProcessDO routeProcess, Long productId) {
+        if (routeProcess == null) {
+            return productId;
+        }
+        if (routeProcess.getId() != null) {
+            // 宸ヨ壓璺嚎妯℃澘锛氭寜浜у嚭褰掑睘杩囨护鍑哄綋鍓嶄骇鍝佺殑浜у嚭
+            List<Long> outputIds = routeProcessService
+                    .getOutputItemIdsByRouteProcessId(routeProcess.getId(), productId);
+            if (outputIds != null) {
+                // 鏈夊綊灞為厤缃細璇ヤ骇鍝佹棤浜у嚭鏃跺彇宸ュ崟鎴愬搧锛沶ull 琛ㄧず瀛橀噺鏃犲綊灞炴暟鎹紝缁х画璧拌仛鍚堝厹搴�
+                return CollUtil.isNotEmpty(outputIds) ? outputIds.get(0) : productId;
+            }
+        }
+        // 宸ュ崟宸ュ簭蹇収 / 瀛橀噺鏃犲綊灞炴暟鎹細浜у嚭宸叉寜浜у搧杩囨护锛屽彇绗竴涓�
+        String outputItemId = routeProcess.getOutputItemId();
+        if (outputItemId != null && !outputItemId.isBlank()) {
+            return Long.valueOf(outputItemId.split(",")[0].trim());
+        }
+        return productId;
+    }
+
     @Override
     public void deleteTask(Long id) {
         // 1. 鏍¢獙瀛樺湪涓旈潪缁堟��
@@ -233,6 +339,11 @@
     }
 
     @Override
+    public MesProTaskDO getTaskByCode(String code) {
+        return taskMapper.selectByCode(code);
+    }
+
+    @Override
     public PageResult<MesProTaskDO> getTaskPage(MesProTaskPageReqVO pageReqVO) {
         return taskMapper.selectPage(pageReqVO);
     }
@@ -240,6 +351,20 @@
     @Override
     public PageResult<MesProTaskDO> getPendingFeedbackPage(MesProTaskPageReqVO pageReqVO) {
         return taskMapper.selectPendingFeedbackPage(pageReqVO);
+    }
+
+    @Override
+    public Map<Long, Long> getPendingFeedbackCountByWorkstation() {
+        List<Map<String, Object>> rows = taskMapper.selectPendingFeedbackCountByWorkstation();
+        Map<Long, Long> result = new HashMap<>(rows.size());
+        for (Map<String, Object> row : rows) {
+            Object workstationId = row.get("workstation_id");
+            Object count = row.get("pending_count");
+            if (workstationId instanceof Number && count instanceof Number) {
+                result.put(((Number) workstationId).longValue(), ((Number) count).longValue());
+            }
+        }
+        return result;
     }
 
     @Override
@@ -318,6 +443,33 @@
     }
 
     @Override
+    public Integer calculateDuration(Long workstationId, BigDecimal quantity) {
+        if (quantity == null || quantity.compareTo(BigDecimal.ZERO) <= 0) {
+            return 0;
+        }
+        // 1. 鏌ヨ宸ヤ綔绔欑粦瀹氱殑璁惧
+        List<MesMdWorkstationMachineDO> machines = workstationMachineService
+                .getWorkstationMachineListByWorkstationId(workstationId);
+        if (CollUtil.isEmpty(machines)) {
+            return 0;
+        }
+        // 2. 鑾峰彇璁惧璇︽儏锛岃绠楁�绘晥鐜�
+        Set<Long> machineryIds = convertSet(machines, MesMdWorkstationMachineDO::getMachineryId);
+        List<MesDvMachineryDO> machineryList = machineryService.getMachineryList(machineryIds);
+        BigDecimal totalEfficiency = BigDecimal.ZERO;
+        for (MesDvMachineryDO m : machineryList) {
+            BigDecimal efficiency = m.getProductionEfficiency() != null ? m.getProductionEfficiency() : BigDecimal.ZERO;
+            Integer belt = m.getConveyorBelt() != null && m.getConveyorBelt() > 0 ? m.getConveyorBelt() : 1;
+            totalEfficiency = totalEfficiency.add(efficiency.multiply(BigDecimal.valueOf(belt)));
+        }
+        if (totalEfficiency.compareTo(BigDecimal.ZERO) <= 0) {
+            return 0;
+        }
+        // 3. 鐢熶骇鏃堕暱锛堝皬鏃讹級 = ceil(鎺掍骇鏁伴噺 / 鎬绘晥鐜�)锛屾晥鐜囧崟浣嶄负姣忓皬鏃朵骇閲�
+        return quantity.divide(totalEfficiency, 0, java.math.RoundingMode.CEILING).intValue();
+    }
+
+    @Override
     public MesProTaskAutoGeneratePreviewRespVO previewAutoGenerate(MesProTaskAutoGenerateReqVO reqVO) {
         // 1. 鏍¢獙宸ュ崟宸茬‘璁�
         MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
@@ -370,7 +522,7 @@
         List<Long> taskIds = new java.util.ArrayList<>();
         for (MesProRouteProcessDO rp : routeProcesses) {
             cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO proc = procMap.get(rp.getProcessId());
-            Long itemId = rp.getOutputItemId() != null ? rp.getOutputItemId() : workOrder.getProductId();
+            Long itemId = resolveTaskItemId(rp, workOrder.getProductId());
             cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO item = itemService.validateItemExists(itemId);
             MesProTaskDO task = new MesProTaskDO()
                     .setWorkOrderId(workOrder.getId())
@@ -383,6 +535,7 @@
                     .setStatus(MesProTaskStatusEnum.PREPARE.getStatus())
                     .setClientId(workOrder.getClientId())
                     .setColorCode(rp.getColorCode());
+            freezeTaskParamConfig(task);
             taskMapper.insert(task);
             taskIds.add(task.getId());
         }
@@ -391,4 +544,56 @@
         return taskIds;
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void pauseTask(MesProTaskPauseReqVO pauseReqVO) {
+        // 1. 鏍¢獙浠诲姟瀛樺湪
+        MesProTaskDO task = validateTaskExists(pauseReqVO.getId());
+        // 2. 鏍¢獙鐘舵�佹槸鍚﹀厑璁稿仠宸ワ紙濡傚繀椤绘槸 鐢熶骇涓� 鎴� 閮ㄥ垎瀹屾垚锛�
+        if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PRODUCING.getStatus(),
+                MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus())) {
+            throw exception(PRO_TASK_CANNOT_PAUSE);
+        }
+        
+        // 3. 淇敼浠诲姟鐘舵�佷负宸插仠宸�
+        taskMapper.updateById(new MesProTaskDO().setId(task.getId()).setStatus(MesProTaskStatusEnum.PAUSED.getStatus()));
+
+        // 4. 鍒涘缓鍋滃伐璁板綍
+        MesProTaskPauseRecordDO pauseRecord = new MesProTaskPauseRecordDO()
+                .setTaskId(task.getId())
+                .setWorkOrderId(task.getWorkOrderId())
+                .setWorkstationId(task.getWorkstationId())
+                .setReason(pauseReqVO.getReason())
+                .setRemark(pauseReqVO.getRemark())
+                .setPauseTime(LocalDateTime.now())
+                .setPauseUserId(SecurityFrameworkUtils.getLoginUserId());
+        taskPauseRecordMapper.insert(pauseRecord);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void resumeTask(MesProTaskResumeReqVO resumeReqVO) {
+        // 1. 鏍¢獙浠诲姟瀛樺湪
+        MesProTaskDO task = validateTaskExists(resumeReqVO.getId());
+        // 2. 鏍¢獙鐘舵�佸繀椤绘槸宸插仠宸�
+        if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PAUSED.getStatus())) {
+            throw exception(PRO_TASK_NOT_PAUSED);
+        }
+
+        // 3. 鎭㈠浠诲姟鐘舵��
+        Integer resumeStatus = task.getProducedQuantity() != null
+                && task.getProducedQuantity().signum() > 0
+                ? MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus()
+                : MesProTaskStatusEnum.PRODUCING.getStatus();
+        taskMapper.updateById(new MesProTaskDO().setId(task.getId()).setStatus(resumeStatus));
+
+        // 4. 鏇存柊鏈�鏂扮殑涓�鏉″仠宸ヨ褰�
+        MesProTaskPauseRecordDO pauseRecord = taskPauseRecordMapper.selectLastUnresumedByTaskId(task.getId());
+        if (pauseRecord != null) {
+            pauseRecord.setResumeTime(LocalDateTime.now());
+            pauseRecord.setResumeUserId(SecurityFrameworkUtils.getLoginUserId());
+            taskPauseRecordMapper.updateById(pauseRecord);
+        }
+    }
+
 }

--
Gitblit v1.9.3