From 7bd10d34e8a8dc2437ec8551eb511e37634d7306 Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期一, 02 十一月 2026 16:51:52 +0800
Subject: [PATCH] feat(mes): 生产计划审批改走协同办公,修复工单领料与产成品入库校验

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/task/MesProTaskServiceImpl.java |  131 ++++++++++++++++++++++++++++++++++++-------
 1 files changed, 109 insertions(+), 22 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 71194e3..c31ce78 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
@@ -76,6 +76,15 @@
 public class MesProTaskServiceImpl implements MesProTaskService {
 
     @Resource
+    private cn.iocoder.yudao.module.mes.service.pro.feedback.MesProFeedbackEquipmentService equipmentService;
+
+    @Resource
+    private cn.iocoder.yudao.module.mes.service.pro.task.schedule.MesProScheduleGuardService scheduleGuard;
+
+    @Resource
+    private cn.iocoder.yudao.module.mes.dal.mysql.pro.workorder.MesProWorkOrderMapper scheduleWorkOrderMapper;
+
+    @Resource
     private MesProTaskMapper taskMapper;
 
     @Resource
@@ -124,23 +133,23 @@
     private MesDvMachineryService machineryService;
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = org.springframework.transaction.annotation.Isolation.SERIALIZABLE)
     public Long createTask(MesProTaskSaveReqVO createReqVO) {
+        scheduleGuard.lock();
         // 1. 鏍¢獙鍏宠仈鏁版嵁瀛樺湪
-        MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderExists(createReqVO.getWorkOrderId());
-        workstationService.validateWorkstationExists(createReqVO.getWorkstationId());
-        routeService.validateRouteExists(createReqVO.getRouteId());
-        processService.validateProcessExistsAndEnable(createReqVO.getProcessId());
+        MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(createReqVO.getWorkOrderId());
+        workstationService.validateWorkstationExistsAndEnable(createReqVO.getWorkstationId());
 
         // 2. 鑷姩纭畾 itemId锛氫紭鍏堝墠绔紶鍏� 鈫� 宸ヨ壓璺嚎宸ュ簭鐨勪骇鍑轰骇鍝� 鈫� 宸ュ崟鎴愬搧
+        // 鏈厤缃伐搴�/宸ヨ壓璺嚎鏃讹紝浠诲姟鐩存帴鎸夊伐鍗曟垚鍝佷骇鍑�
         Long itemId = createReqVO.getItemId();
-        if (itemId == null) {
+        if (itemId == null && createReqVO.getRouteId() != null && createReqVO.getProcessId() != null) {
             MesProRouteProcessDO routeProcess = routeProcessService
                     .getRouteProcessByRouteIdAndProcessId(createReqVO.getRouteId(), createReqVO.getProcessId());
-            if (routeProcess != null && routeProcess.getOutputItemId() != null) {
-                itemId = Long.valueOf(routeProcess.getOutputItemId().split(",")[0].trim());
-            } else {
-                itemId = workOrder.getProductId();
-            }
+            itemId = resolveTaskItemId(routeProcess, workOrder.getProductId());
+        }
+        if (itemId == null) {
+            itemId = workOrder.getProductId();
         }
         MesMdItemDO item = itemService.validateItemExists(itemId);
 
@@ -153,6 +162,7 @@
         if (task.getClientId() == null && workOrder.getClientId() != null) {
             task.setClientId(workOrder.getClientId());
         }
+        scheduleGuard.validate(workOrder, task, null);
         // 2.2 鎻掑叆
         taskMapper.insert(task);
         // 2.2.1 鍙傛暟蹇収锛氫紭鍏堜娇鐢ㄦā鏉匡紱鏃犳ā鏉挎椂鍥為��鍒板伐搴忎富鏁版嵁缁戝畾鐨勫伐鑹哄弬鏁�
@@ -160,6 +170,7 @@
 
         // 2.3 淇濆瓨鎸囧畾鎶ュ伐浜哄憳
         updateTaskAssignees(task.getId(), createReqVO.getAssigneeUserIds());
+        synchronizeScheduledQuantity(workOrder, task.getRouteId());
 
         return task.getId();
     }
@@ -180,21 +191,17 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class, isolation = org.springframework.transaction.annotation.Isolation.SERIALIZABLE)
     public void updateTask(MesProTaskSaveReqVO updateReqVO) {
+        scheduleGuard.lock();
         // 1.1 鏍¢獙瀛樺湪
-        MesProTaskDO task = validateTaskExists(updateReqVO.getId());
+        MesProTaskDO task = validateTaskNotFinished(updateReqVO.getId());
         // 1.2 鏍¢獙鍏宠仈鏁版嵁瀛樺湪
         if (updateReqVO.getWorkOrderId() != null) {
             workOrderService.validateWorkOrderExists(updateReqVO.getWorkOrderId());
         }
         if (updateReqVO.getWorkstationId() != null) {
             workstationService.validateWorkstationExists(updateReqVO.getWorkstationId());
-        }
-        if (updateReqVO.getRouteId() != null) {
-            routeService.validateRouteExists(updateReqVO.getRouteId());
-        }
-        if (updateReqVO.getProcessId() != null) {
-            processService.validateProcessExistsAndEnable(updateReqVO.getProcessId());
         }
         MesMdItemDO item;
         if (updateReqVO.getItemId() != null) {
@@ -204,6 +211,15 @@
         }
         BigDecimal quantity = updateReqVO.getQuantity() != null ? updateReqVO.getQuantity() : task.getQuantity();
 
+        MesProTaskDO candidate = BeanUtils.toBean(task, MesProTaskDO.class)
+                .setWorkOrderId(updateReqVO.getWorkOrderId() != null ? updateReqVO.getWorkOrderId() : task.getWorkOrderId())
+                .setProcessId(updateReqVO.getProcessId() != null ? updateReqVO.getProcessId() : task.getProcessId())
+                .setWorkstationId(updateReqVO.getWorkstationId() != null ? updateReqVO.getWorkstationId() : task.getWorkstationId())
+                .setQuantity(quantity)
+                .setStartTime(updateReqVO.getStartTime() != null ? updateReqVO.getStartTime() : task.getStartTime())
+                .setEndTime(updateReqVO.getEndTime() != null ? updateReqVO.getEndTime() : task.getEndTime());
+        scheduleGuard.validate(workOrderService.validateWorkOrderConfirmed(candidate.getWorkOrderId()), candidate, task.getId());
+
         // 2. 鏇存柊
         MesProTaskDO updateObj = BeanUtils.toBean(updateReqVO, MesProTaskDO.class)
                 .setName(buildTaskName(item, quantity));
@@ -211,6 +227,37 @@
         
         // 3. 鏇存柊鎸囧畾鎶ュ伐浜哄憳锛堟瘮瀵规洿鏂帮級
         updateTaskAssignees(task.getId(), updateReqVO.getAssigneeUserIds());
+        synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(candidate.getWorkOrderId()),
+                updateReqVO.getRouteId() != null ? updateReqVO.getRouteId() : task.getRouteId());
+        if (!java.util.Objects.equals(task.getWorkOrderId(), candidate.getWorkOrderId())) {
+            synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(task.getWorkOrderId()), task.getRouteId());
+        }
+    }
+
+    /** 宸ュ崟鎺掍骇閲忔寜鍚勫伐搴忕疮璁℃帓浜ч噺鐨勬渶灏忓�艰绠楋紝浠诲姟淇敼/鍒犻櫎鍚庡悓姝ワ紝閬垮厤鏃犳硶琛ユ帓銆� */
+    private void synchronizeScheduledQuantity(MesProWorkOrderDO order, Long routeId) {
+        List<MesProRouteProcessDO> required = buildRouteProcessList(routeId, order.getId());
+        List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(order.getId());
+        BigDecimal orderQuantity = order.getQuantity() == null ? BigDecimal.ZERO : order.getQuantity();
+        BigDecimal scheduled;
+        if (CollUtil.isEmpty(required)) {
+            // 鏈厤缃伐搴�/宸ヨ壓璺嚎锛氭暣寮犲伐鍗曞彧鏈変竴涓敓浜ф楠わ紝宸叉帓浜ч噺鎸変换鍔℃暟閲忓悎璁�
+            scheduled = tasks.stream()
+                    .filter(task -> !java.util.Objects.equals(task.getStatus(), MesProTaskStatusEnum.CANCELED.getStatus()))
+                    .map(task -> task.getQuantity() == null ? BigDecimal.ZERO : task.getQuantity())
+                    .reduce(BigDecimal.ZERO, BigDecimal::add)
+                    .min(orderQuantity);
+        } else {
+            scheduled = orderQuantity;
+            for (var process : required) {
+                BigDecimal amount = tasks.stream().filter(task -> java.util.Objects.equals(task.getProcessId(), process.getProcessId())
+                                && !java.util.Objects.equals(task.getStatus(), MesProTaskStatusEnum.CANCELED.getStatus()))
+                        .map(task -> task.getQuantity() == null ? BigDecimal.ZERO : task.getQuantity())
+                        .reduce(BigDecimal.ZERO, BigDecimal::add);
+                scheduled = scheduled.min(amount);
+            }
+        }
+        scheduleWorkOrderMapper.updateById(new MesProWorkOrderDO().setId(order.getId()).setQuantityScheduled(scheduled));
     }
 
     private void updateTaskAssignees(Long taskId, List<Long> assigneeUserIds) {
@@ -293,7 +340,10 @@
             }
             return result;
         }
-        // 2. 鍥為��鍒板伐鑹鸿矾绾挎ā鏉垮伐搴�
+        // 2. 鍥為��鍒板伐鑹鸿矾绾挎ā鏉垮伐搴忥紱鏈厤缃伐鑹鸿矾绾挎椂瑙嗕负鍗曞伐搴忕敓浜�
+        if (routeId == null) {
+            return new java.util.ArrayList<>();
+        }
         List<MesProRouteProcessDO> routeProcesses = routeProcessService.getRouteProcessListByRouteId(routeId);
         if (CollUtil.isEmpty(routeProcesses)) {
             throw exception(cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_ROUTE_ENABLE_NO_PROCESS);
@@ -301,10 +351,31 @@
         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) {
+                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
+    @Transactional(rollbackFor = Exception.class)
     public void deleteTask(Long id) {
+        scheduleGuard.lock();
         // 1. 鏍¢獙瀛樺湪涓旈潪缁堟��
-        validateTaskNotFinished(id);
+        MesProTaskDO task = validateTaskNotFinished(id);
         // 2. 鏍¢獙浠诲姟涓嬫棤鎶ュ伐璁板綍锛岄伩鍏嶅垹闄ゅ悗鎶ュ伐杩芥函鏂摼
         if (feedbackMapper.selectCountByTaskId(id) > 0) {
             throw exception(PRO_TASK_HAS_FEEDBACK);
@@ -313,6 +384,7 @@
         taskMapper.deleteById(id);
         // 4. 鍒犻櫎鍏宠仈鐨勪汉鍛�
         taskAssigneeMapper.deleteByTaskId(id);
+        synchronizeScheduledQuantity(workOrderService.validateWorkOrderExists(task.getWorkOrderId()), task.getRouteId());
     }
 
     @Override
@@ -373,7 +445,9 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void finishTaskByOrderId(Long workOrderId) {
+        equipmentService.lock();
         // 1. 璇诲彇宸ュ崟涓嬫墍鏈変换鍔★紝杩囨护鎺夊凡瀹屾垚/宸插彇娑堢殑
         List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(workOrderId);
         LocalDateTime now = LocalDateTime.now();
@@ -385,10 +459,13 @@
         if (CollUtil.isNotEmpty(updateList)) {
             taskMapper.updateBatch(updateList);
         }
+        equipmentService.afterTaskStateChanged(tasks);
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void cancelTaskByOrderId(Long workOrderId) {
+        equipmentService.lock();
         // 1. 璇诲彇宸ュ崟涓嬫墍鏈変换鍔★紝杩囨护鎺夊凡瀹屾垚/宸插彇娑堢殑
         List<MesProTaskDO> tasks = taskMapper.selectListByWorkOrderId(workOrderId);
         LocalDateTime now = LocalDateTime.now();
@@ -400,6 +477,7 @@
         if (CollUtil.isNotEmpty(updateList)) {
             taskMapper.updateBatch(updateList);
         }
+        equipmentService.afterTaskStateChanged(tasks);
     }
 
     @Override
@@ -485,6 +563,7 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public List<Long> autoGenerateTasks(MesProTaskAutoGenerateReqVO reqVO) {
+        scheduleGuard.lock();
         // 1. 鏍¢獙宸ュ崟宸茬‘璁�
         MesProWorkOrderDO workOrder = workOrderService.validateWorkOrderConfirmed(reqVO.getWorkOrderId());
         // 2. 纭畾宸ヨ壓璺嚎锛氫紭鍏堣姹傛寚瀹� 鈫� 宸ュ崟鍏宠仈 鈫� 浜у搧鍏宠仈
@@ -502,7 +581,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 ? Long.valueOf(rp.getOutputItemId().split(",")[0].trim()) : 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())
@@ -516,6 +595,7 @@
                     .setParamTemplateId(rp.getParamTemplateId())
                     .setClientId(workOrder.getClientId())
                     .setColorCode(rp.getColorCode());
+            scheduleGuard.validate(workOrder, task, null);
             taskMapper.insert(task);
             // 鍙傛暟蹇収锛氫紭鍏堜娇鐢ㄦā鏉匡紱鏃犳ā鏉挎椂鍥為��鍒板伐搴忎富鏁版嵁缁戝畾鐨勫伐鑹哄弬鏁�
             createTaskParamRecordWithFallback(task);
@@ -529,10 +609,12 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void pauseTask(MesProTaskPauseReqVO pauseReqVO) {
+        equipmentService.lock();
         // 1. 鏍¢獙浠诲姟瀛樺湪
         MesProTaskDO task = validateTaskExists(pauseReqVO.getId());
         // 2. 鏍¢獙鐘舵�佹槸鍚﹀厑璁稿仠宸ワ紙濡傚繀椤绘槸 鐢熶骇涓� 鎴� 閮ㄥ垎瀹屾垚锛�
-        if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PREPARE.getStatus())) {
+        if (!ObjectUtils.equalsAny(task.getStatus(), MesProTaskStatusEnum.PREPARE.getStatus(),
+                MesProTaskStatusEnum.PRODUCING.getStatus(), MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus())) {
             throw exception(PRO_TASK_CANNOT_PAUSE);
         }
         
@@ -549,11 +631,13 @@
                 .setPauseTime(LocalDateTime.now())
                 .setPauseUserId(SecurityFrameworkUtils.getLoginUserId());
         taskPauseRecordMapper.insert(pauseRecord);
+        equipmentService.afterTaskStateChanged(List.of(task));
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void resumeTask(MesProTaskResumeReqVO resumeReqVO) {
+        equipmentService.lock();
         // 1. 鏍¢獙浠诲姟瀛樺湪
         MesProTaskDO task = validateTaskExists(resumeReqVO.getId());
         // 2. 鏍¢獙鐘舵�佸繀椤绘槸宸插仠宸�
@@ -562,8 +646,11 @@
         }
 
         // 3. 鎭㈠浠诲姟鐘舵��
-        Integer resumeStatus = MesProTaskStatusEnum.PREPARE.getStatus();
+        Integer resumeStatus = task.getProducedQuantity() != null && task.getProducedQuantity().signum() > 0
+                ? MesProTaskStatusEnum.PARTIAL_FINISHED.getStatus() : MesProTaskStatusEnum.PREPARE.getStatus();
         taskMapper.updateById(new MesProTaskDO().setId(task.getId()).setStatus(resumeStatus));
+        equipmentService.validateResume(task);
+        equipmentService.afterTaskStateChanged(List.of(task));
 
         // 4. 鏇存柊鏈�鏂扮殑涓�鏉″仠宸ヨ褰�
         MesProTaskPauseRecordDO pauseRecord = taskPauseRecordMapper.selectLastUnresumedByTaskId(task.getId());

--
Gitblit v1.9.3