From 3ff6604c75c887f4056ee03545d204bc9323286c Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 15 十月 2026 17:47:19 +0800
Subject: [PATCH] feat(mes): 新增报工设备快照功能并优化状态校验

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java |  117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 111 insertions(+), 6 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
index 016e10a..5a10283 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java
@@ -12,6 +12,7 @@
 import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackTypeEnum;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProcessDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO;
@@ -24,6 +25,7 @@
 import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProTaskStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmQualityStatusEnum;
+import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
 import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProcessService;
 import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
@@ -52,6 +54,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -68,6 +71,9 @@
 public class MesProFeedbackServiceImpl implements MesProFeedbackService {
 
     @Resource
+    private MesProFeedbackEquipmentService equipmentService;
+
+    @Resource
     private MesProFeedbackMapper feedbackMapper;
 
     @Resource
@@ -77,6 +83,8 @@
     private MesProWorkOrderParamRecordService paramRecordService;
     @Resource
     private MesProRouteProcessService routeProcessService;
+    @Resource
+    private MesProProcessService processService;
     @Resource
     private MesProWorkOrderBomService workOrderBomService;
     @Resource
@@ -122,6 +130,7 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void updateFeedback(MesProFeedbackSaveReqVO updateReqVO) {
+        equipmentService.lock();
         // 1.1 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
         validateFeedbackStatusPrepare(updateReqVO.getId());
         // 1.2 鏍¢獙涓氬姟鏁版嵁
@@ -131,6 +140,7 @@
         MesProFeedbackDO updateObj = BeanUtils.toBean(updateReqVO, MesProFeedbackDO.class)
                 .setItemId(task.getItemId());
         feedbackMapper.updateById(updateObj);
+        equipmentService.resetSnapshot(updateReqVO.getId());
         // 3. 淇濆瓨鍙傛暟鍊硷紱蹇呭~鍙傛暟鏍¢獙澶辫触鏃舵暣浣撳洖婊�
         saveParamValues(task.getId(), updateReqVO.getParamValues());
     }
@@ -147,7 +157,9 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void deleteFeedback(Long id) {
+        equipmentService.lock();
         // 1. 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
         validateFeedbackStatusPrepare(id);
 
@@ -181,13 +193,24 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public void submitFeedback(MesProFeedbackSubmitReqVO reqVO) {
+        equipmentService.lock();
         Long id = reqVO.getId();
         // 1. 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
         MesProFeedbackDO feedback = validateFeedbackStatusPrepare(id);
+        equipmentService.validateSubmission(feedback);
+        // 1.1 椤哄簭鎶ュ伐鏍¢獙锛氬悓涓�鐢熶骇宸ュ崟鍐咃紝涓婁竴閬撳伐搴忓繀椤诲凡鎶ュ伐瀹屾垚锛屽惁鍒欎笉鍏佽鎻愪氦
+        if (feedback.getTaskId() != null) {
+            MesProTaskDO submitTask = taskService.getTask(feedback.getTaskId());
+            if (submitTask != null) {
+                validatePreviousProcessFinished(submitTask);
+            }
+        }
 
         MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(feedback.getWorkOrderId());
 
-        if (workOrder != null && Boolean.TRUE.equals(workOrder.getFeedbackApprove()) && cn.hutool.core.util.StrUtil.isNotBlank(workOrder.getProcessDefinitionKey())) {
+        if (!processInstanceApi.isGlobalAutoApprove() && workOrder != null
+                && Boolean.TRUE.equals(workOrder.getFeedbackApprove())
+                && cn.hutool.core.util.StrUtil.isNotBlank(workOrder.getProcessDefinitionKey())) {
             // 2. 鏇存柊鐘舵�佷负瀹℃壒涓紝骞跺惎鍔ㄦ祦绋�
             String processInstanceId = processInstanceApi.createProcessInstance(cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId(),
                     new cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO()
@@ -252,7 +275,9 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void rejectFeedback(Long id) {
+        equipmentService.lock();
         // 1. 鏍¢獙瀛樺湪 + 瀹℃壒涓姸鎬�
         validateFeedbackStatusApproving(id);
 
@@ -264,8 +289,16 @@
     @Override
     @Transactional(rollbackFor = Exception.class)
     public boolean approveFeedback(MesProFeedbackApproveReqVO reqVO) {
+        equipmentService.lock();
         // 1.1 鏍¢獙瀛樺湪 + 瀹℃壒涓姸鎬�
         MesProFeedbackDO feedback = validateFeedbackStatusApproving(reqVO.getId());
+        equipmentService.validateApproval(feedback);
+        try (var equipmentScope = equipmentService.beginApproval(feedback)) {
+            return approveValidatedFeedback(reqVO, feedback);
+        }
+    }
+
+    private boolean approveValidatedFeedback(MesProFeedbackApproveReqVO reqVO, MesProFeedbackDO feedback) {
         // 1.2 鏍¢獙鎶ュ伐鏁伴噺 > 0
         if (feedback.getFeedbackQuantity() == null
                 || feedback.getFeedbackQuantity().compareTo(BigDecimal.ZERO) <= 0) {
@@ -296,16 +329,17 @@
             throw exception(PRO_FEEDBACK_UNCHECK_QUANTITY_EXISTS, feedback.getUncheckQuantity());
         }
 
-        // 3. 鐗╂枡娑堣�楋細鍊掑啿宸ュ簭鎸� BOM 鑷姩鎵f枡锛涢潪鍊掑啿宸ュ簭璺宠繃锛堢墿鏂欒蛋鎵嬪伐棰嗘枡/鎶曟枡锛�
+        // 3. 鐗╂枡娑堣�楋細鍊掑啿宸ュ簭鎸夋湰宸ュ簭鎶曟枡 BOM 鑷姩鎵f枡锛涢潪鍊掑啿宸ュ簭璺宠繃锛堢墿鏂欒蛋鎵嬪伐棰嗘枡/鎶曟枡锛�
         boolean backflushFlag = routeProcess != null && Boolean.TRUE.equals(routeProcess.getBackflushFlag());
         if (backflushFlag) {
-            // 3.1 鏍¢獙鍊掑啿宸ュ簭蹇呴』閰嶇疆 BOM 娑堣�楋紝閬垮厤闈欓粯涓嶆墸鏂�
-            List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderId(
-                    feedback.getWorkOrderId());
+            // 3.1 鏍¢獙鍊掑啿宸ュ簭蹇呴』閰嶇疆鏈伐搴忕殑鎶曟枡 BOM锛岄伩鍏嶉潤榛樹笉鎵f枡
+            //     锛堝彧鏍¢獙褰撳墠鎶ュ伐宸ュ簭鐨勬姇鏂欙紝鍏朵粬宸ュ簭鎶曟枡鐢卞悇鑷伐搴忔姤宸ユ椂鎵i櫎锛�
+            List<MesProWorkOrderBomDO> boms = workOrderBomService.getWorkOrderBomListByWorkOrderIdAndProcessId(
+                    feedback.getWorkOrderId(), feedback.getProcessId());
             if (CollUtil.isEmpty(boms)) {
                 throw exception(PRO_FEEDBACK_BACKFLUSH_BOM_REQUIRED);
             }
-            // 3.2 鐢熸垚娑堣�楄褰曞苟鎵ц鎵e噺锛圔OM 脳 鎶ュ伐鏁伴噺锛孎IFO 鎵g嚎杈瑰簱锛�
+            // 3.2 鐢熸垚娑堣�楄褰曞苟鎵ц鎵e噺锛堟湰宸ュ簭鎶曟枡鐢ㄩ噺姣斾緥 脳 鎶ュ伐鏁伴噺锛孎IFO 鎵g嚎杈瑰簱锛�
             MesWmItemConsumeDO itemConsume = itemConsumeService.generateItemConsume(feedback);
             if (itemConsume != null) {
                 itemConsumeService.finishItemConsume(itemConsume.getId());
@@ -336,6 +370,7 @@
             productProduceService.generateProductProduce(feedback, true, reqVO);
             feedbackMapper.updateById(new MesProFeedbackDO().setId(reqVO.getId())
                     .setStatus(MesProFeedbackStatusEnum.UNCHECK.getStatus()));
+            equipmentService.afterApproved(feedback);
             return false;
         } else {
             // 6.2 鏃犻渶妫�楠岋細鐢熸垚浜у嚭鍗曪紙鎸夊悎鏍�/涓嶅悎鏍兼媶琛岋級锛岀洿鎺ュ畬鎴愬叆搴�
@@ -344,6 +379,7 @@
 
         }
 
+        equipmentService.afterApproved(feedback);
         return true; // 宸插畬鎴�
     }
 
@@ -487,9 +523,75 @@
         return task;
     }
 
+    /**
+     * 椤哄簭鎶ュ伐鏍¢獙锛氬悓涓�鐢熶骇宸ュ崟鍐咃紝涓婁竴閬撳伐搴忓繀椤诲凡鎶ュ伐瀹屾垚锛屽惁鍒欎笉鍏佽鎶ユ湰閬撳伐搴�
+     *
+     * <p>鍒ゅ畾鍙e緞锛氫笂涓�閬撳伐搴忕殑鏈夋晥浠诲姟锛堟湭鍙栨秷锛夌疮璁″鎵归�氳繃鐨勪骇閲忓凡杈惧埌鍏舵帓浜ф暟閲忥紝
+     * 鍗充换涓�鏈夋晥浠诲姟 produced_quantity &lt; quantity 閮借涓烘湭瀹屾垚骞舵嫤鎴��
+     * 涔嬫墍浠ヤ笉渚濊禆浠诲姟 status锛氫换鍔$姸鎬佸彧鏈夋暣鍗曞畬宸�/鍙栨秷鏃舵墠缃粓鎬�
+     * 锛坒inishTaskByOrderId / cancelTaskByOrderId锛夛紝鏃犳硶浣滀负"鍗曢亾宸ュ簭宸插畬鎴�"鐨勪緷鎹紝
+     * produced_quantity 鏄敮涓�瀹炴椂鍙潬鐨勮繘搴︿俊鍙枫��
+     *
+     * @param task 褰撳墠寰呮姤宸ヤ换鍔�
+     */
+    private void validatePreviousProcessFinished(MesProTaskDO task) {
+        if (task == null || task.getWorkOrderId() == null || task.getProcessId() == null) {
+            return; // 鏁版嵁涓嶅畬鏁达紝鏃犳硶鎺ㄥ椤哄簭锛屾斁琛�
+        }
+        // 1. 褰撳墠浠诲姟鎵�灞炲伐搴忓湪宸ュ崟宸ュ簭閾撅紙鎸� sort 鍗囧簭锛変腑鐨勪綅缃�
+        List<MesProWorkOrderProcessDO> processes = workOrderProcessService
+                .getWorkOrderProcessListByWorkOrderId(task.getWorkOrderId());
+        if (CollUtil.isEmpty(processes)) {
+            return; // 宸ュ崟鏈厤缃伐搴忥紝鏀捐
+        }
+        // 鎷疯礉鍚庡啀鎺掑簭锛岄伩鍏嶄慨鏀硅皟鐢ㄦ柟杩斿洖鐨勶紙鍙兘鏄笉鍙彉锛夐泦鍚�
+        processes = new ArrayList<>(processes);
+        processes.sort(Comparator.comparing(MesProWorkOrderProcessDO::getSort,
+                Comparator.nullsLast(Comparator.naturalOrder())));
+        int index = -1;
+        for (int i = 0; i < processes.size(); i++) {
+            if (ObjUtil.equal(processes.get(i).getProcessId(), task.getProcessId())) {
+                index = i;
+                break;
+            }
+        }
+        if (index <= 0) {
+            return; // 褰撳墠宸ュ簭涓嶅湪閾句腑锛堟暟鎹紓甯革紝鏀捐锛夋垨鏈韩灏辨槸绗竴閬撳伐搴忥紝鏃犻渶鍓嶇疆
+        }
+        Long prevProcessId = processes.get(index - 1).getProcessId();
+        if (prevProcessId == null) {
+            return;
+        }
+        // 2. 涓婁竴閬撳伐搴忕殑鏈夋晥浠诲姟锛堟帓闄ゅ凡鍙栨秷锛夌疮璁″緟瀹屾垚閲�
+        List<MesProTaskDO> prevTasks = taskService.getTaskListByWorkOrderIds(
+                        Collections.singletonList(task.getWorkOrderId())).stream()
+                .filter(t -> ObjUtil.equal(t.getProcessId(), prevProcessId))
+                .filter(t -> !ObjUtil.equal(t.getStatus(), MesProTaskStatusEnum.CANCELED.getStatus()))
+                .toList();
+        if (CollUtil.isEmpty(prevTasks)) {
+            return; // 涓婁竴閬撳伐搴忔棤鏈夋晥鐢熶骇浠诲姟锛堝惈鍏ㄩ儴鍙栨秷锛夛紝瑙嗕负鏃犲墠缃姹傦紝鏀捐
+        }
+        BigDecimal remaining = BigDecimal.ZERO;
+        for (MesProTaskDO prevTask : prevTasks) {
+            BigDecimal qty = prevTask.getQuantity() == null ? BigDecimal.ZERO : prevTask.getQuantity();
+            BigDecimal produced = prevTask.getProducedQuantity() == null ? BigDecimal.ZERO : prevTask.getProducedQuantity();
+            if (produced.compareTo(qty) < 0) {
+                remaining = remaining.add(qty.subtract(produced));
+            }
+        }
+        if (remaining.compareTo(BigDecimal.ZERO) <= 0) {
+            return; // 涓婁竴閬撳伐搴忓凡鍏ㄩ儴鎶ユ弧锛屾斁琛�
+        }
+        // 3. 鎷︽埅骞舵彁绀轰笂涓�閬撳伐搴忓悕绉颁笌鍓╀綑寰呮姤鏁伴噺
+        MesProProcessDO prevProcess = processService.getProcess(prevProcessId);
+        String prevProcessName = prevProcess != null ? prevProcess.getName() : String.valueOf(prevProcessId);
+        throw exception(PRO_FEEDBACK_PREV_PROCESS_NOT_FINISHED, prevProcessName, remaining);
+    }
+
     @Override
     @Transactional(rollbackFor = Exception.class)
     public MesProFeedbackQuickCreateRespVO quickCreateFeedback(MesProFeedbackQuickCreateReqVO reqVO) {
+        equipmentService.lock();
         // 1. 鏍¢獙浠诲姟瀛樺湪涓旈潪缁堟��
         MesProTaskDO task = taskService.validateTaskNotFinished(reqVO.getTaskId());
 
@@ -497,6 +599,8 @@
         if (!MesProTaskStatusEnum.canFeedback(task.getStatus())) {
             throw exception(PRO_FEEDBACK_CANNOT_CREATE_WHEN_PAUSED);
         }
+        // 1.1 椤哄簭鎶ュ伐鏍¢獙锛氬悓涓�鐢熶骇宸ュ崟鍐咃紝涓婁竴閬撳伐搴忓繀椤诲凡鎶ュ伐瀹屾垚锛屽惁鍒欎笉鍏佽鎶ュ伐
+        validatePreviousProcessFinished(task);
 
         // 2. 鏍¢獙宸ヨ壓璺嚎宸ュ簭閰嶇疆
         MesProRouteProcessDO routeProcess = routeProcessService.getRouteProcessByRouteIdAndProcessId(
@@ -524,6 +628,7 @@
                 .setFeedbackTime(LocalDateTime.now())
                 .setStatus(MesProFeedbackStatusEnum.APPROVING.getStatus())
                 .setRemark(reqVO.getRemark());
+        equipmentService.validateSubmission(feedback);
         feedbackMapper.insert(feedback);
         saveParamValues(task.getId(), reqVO.getParamValues());
         // 5. 杩斿洖缁撴灉

--
Gitblit v1.9.3