From af6bafbb985e1972f938aef9bfedbe6549f749ad Mon Sep 17 00:00:00 2001
From: liu <2021943741@qq.com>
Date: 星期一, 07 九月 2026 16:46:48 +0800
Subject: [PATCH] feat(mes): 生产报工按工单工序顺序控制,上一道报满后才能报下一道

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImpl.java     |   79 +++++++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java                           |    4 
 yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java |  307 +++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 390 insertions(+), 0 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
index 35d9cc8..0beb4df 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -328,6 +328,10 @@
     ErrorCode PRO_FEEDBACK_UNCHECK_QUANTITY_EXISTS = new ErrorCode(1_040_506_012, "褰撳墠鎶ュ伐鍗曟湭瀹屾垚妫�楠岋紙寰呮鏁伴噺锛歿}锛夛紝鏃犳硶鎵ц鎶ュ伐");
     ErrorCode PRO_FEEDBACK_BACKFLUSH_BOM_REQUIRED = new ErrorCode(1_040_506_013, "褰撳墠宸ュ簭涓哄�掑啿宸ュ簭锛屼絾鏈厤缃瓸OM娑堣�楋紝鏃犳硶鎶ュ伐");
     ErrorCode PRO_FEEDBACK_CANNOT_CREATE_WHEN_PAUSED = new ErrorCode(1_040_506_014, "鐢熶骇浠诲姟澶勪簬鍋滃伐鐘舵�侊紝鏃犳硶鎶ュ伐");
+    ErrorCode PRO_FEEDBACK_PENDING_PRODUCE_UNREPAIRABLE = new ErrorCode(1_040_506_015,
+            "鎶ュ伐瀵瑰簲鐨勪骇鍑哄叆搴撳崟宸插瓨鍦ㄥ鐞嗙粨鏋滐紝鏃犳硶鑷姩琛ョ敓鎴愬緟妫�琛岋紝璇疯繍琛岃川妫�鎶ュ伐鏁版嵁淇鑴氭湰鏍稿");
+    ErrorCode PRO_FEEDBACK_PREV_PROCESS_NOT_FINISHED = new ErrorCode(1_040_506_016,
+            "涓婁竴閬撳伐搴忋�寋}銆嶅皻鏈姤宸ュ畬鎴愶紙鍓╀綑 {}锛夛紝璇峰厛瀹屾垚涓婁竴閬撳伐搴忕殑鎶ュ伐");
 
     // ========== MES 鐢熶骇绠$悊-鐢熶骇娴佽浆鍗★紙1-040-507-000锛� ==========
     ErrorCode PRO_CARD_NOT_EXISTS = new ErrorCode(1_040_507_000, "鐢熶骇娴佽浆鍗′笉瀛樺湪");
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 d3f2a0a..48db7a0 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;
@@ -77,6 +80,8 @@
     private MesProWorkOrderParamRecordService paramRecordService;
     @Resource
     private MesProRouteProcessService routeProcessService;
+    @Resource
+    private MesProProcessService processService;
     @Resource
     private MesProWorkOrderBomService workOrderBomService;
     @Resource
@@ -184,6 +189,13 @@
         Long id = reqVO.getId();
         // 1. 鏍¢獙瀛樺湪 + 鑽夌鐘舵��
         MesProFeedbackDO feedback = validateFeedbackStatusPrepare(id);
+        // 1.1 椤哄簭鎶ュ伐鏍¢獙锛氬悓涓�鐢熶骇宸ュ崟鍐咃紝涓婁竴閬撳伐搴忓繀椤诲凡鎶ュ伐瀹屾垚锛屽惁鍒欎笉鍏佽鎻愪氦
+        if (feedback.getTaskId() != null) {
+            MesProTaskDO submitTask = taskService.getTask(feedback.getTaskId());
+            if (submitTask != null) {
+                validatePreviousProcessFinished(submitTask);
+            }
+        }
 
         MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(feedback.getWorkOrderId());
 
@@ -488,6 +500,71 @@
         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) {
@@ -498,6 +575,8 @@
         if (!MesProTaskStatusEnum.canFeedback(task.getStatus())) {
             throw exception(PRO_FEEDBACK_CANNOT_CREATE_WHEN_PAUSED);
         }
+        // 1.1 椤哄簭鎶ュ伐鏍¢獙锛氬悓涓�鐢熶骇宸ュ崟鍐咃紝涓婁竴閬撳伐搴忓繀椤诲凡鎶ュ伐瀹屾垚锛屽惁鍒欎笉鍏佽鎶ュ伐
+        validatePreviousProcessFinished(task);
 
         // 2. 鏍¢獙宸ヨ壓璺嚎宸ュ簭閰嶇疆
         MesProRouteProcessDO routeProcess = routeProcessService.getRouteProcessByRouteIdAndProcessId(
diff --git a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java
index 4cefcbd..a086275 100644
--- a/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java
+++ b/yudao-module-mes/src/test/java/cn/iocoder/yudao/module/mes/service/pro/feedback/MesProFeedbackServiceImplTest.java
@@ -1,18 +1,35 @@
 package cn.iocoder.yudao.module.mes.service.pro.feedback;
 
 import cn.hutool.core.collection.ListUtil;
+import cn.iocoder.yudao.framework.common.exception.ServiceException;
 import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemBatchConfigApi;
+import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackApproveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackQuickCreateReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackQuickCreateRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.feedback.vo.MesProFeedbackSubmitReqVO;
 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.MesProWorkOrderProcessDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.productproduce.MesWmProductProduceLineDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
 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.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
+import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
+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;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderParamRecordService;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
 import cn.iocoder.yudao.module.mes.service.wm.itemconsume.MesWmItemConsumeService;
 import cn.iocoder.yudao.module.mes.service.wm.productproduce.MesWmProductProduceLineService;
@@ -23,6 +40,8 @@
 import org.springframework.test.context.bean.override.mockito.MockitoBean;
 
 import java.math.BigDecimal;
+import java.util.Collections;
+import java.util.List;
 
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
@@ -57,6 +76,24 @@
     private MesWmProductProduceService productProduceService;
     @MockitoBean
     private MesWmProductProduceLineService produceLineService;
+    @MockitoBean
+    private MesProWorkOrderParamRecordService paramRecordService;
+    @MockitoBean
+    private MesProWorkOrderBomService workOrderBomService;
+    @MockitoBean
+    private MesProWorkOrderProcessService workOrderProcessService;
+    @MockitoBean
+    private MesProProcessService processService;
+    @MockitoBean
+    private MesProMpsApi mpsApi;
+    @MockitoBean
+    private MesMdAutoCodeRecordService autoCodeRecordService;
+    @MockitoBean
+    private MesMdmItemService mdmItemService;
+    @MockitoBean
+    private MdmItemBatchConfigApi itemBatchConfigApi;
+    @MockitoBean
+    private BpmProcessInstanceApi processInstanceApi;
 
     @Test
     public void testUpdateProFeedbackWhenIpqcFinish_success_withUnqualified() {
@@ -405,4 +442,274 @@
         verify(productProduceService, never()).generateProductProduce(any(), anyBoolean(), any());
     }
 
+    // ==================== 椤哄簭鎶ュ伐锛堜笂涓�閬撳伐搴忓畬鎴愬悗鎵嶈兘鎶ヤ笅涓�閬擄級娴嬭瘯 ====================
+
+    /**
+     * 鏋勯��"涓婇亾宸ュ簭锛坧rev锛�+ 褰撳墠宸ュ簭锛坈ur锛�"鐨勫伐鍗曞伐搴忛摼锛堟晠鎰忎贡搴忎紶鍏ワ紝楠岃瘉鎸� sort 鎺掑簭鍚庡彇鍓嶉┍锛�
+     */
+    private List<MesProWorkOrderProcessDO> buildTwoProcessChain(Long workOrderId, Long prevProcessId,
+                                                                Long curProcessId) {
+        return ListUtil.of(
+                MesProWorkOrderProcessDO.builder().workOrderId(workOrderId).processId(curProcessId).sort(2).build(),
+                MesProWorkOrderProcessDO.builder().workOrderId(workOrderId).processId(prevProcessId).sort(1).build());
+    }
+
+    @Test
+    public void testQuickCreateFeedback_blockedWhenPreviousProcessNotFinished() {
+        // 鍑嗗锛氬綋鍓嶄换鍔″湪绗� 2 閬撳伐搴忥紝涓婇亾宸ュ簭浠诲姟鍙仛浜� 60/100
+        Long workOrderId = randomLongId();
+        Long routeId = randomLongId();
+        Long prevProcessId = randomLongId();
+        Long curProcessId = randomLongId();
+        Long curTaskId = randomLongId();
+
+        MesProTaskDO curTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setId(curTaskId);
+            o.setWorkOrderId(workOrderId);
+            o.setRouteId(routeId);
+            o.setProcessId(curProcessId);
+            o.setQuantity(BigDecimal.valueOf(100));
+            o.setProducedQuantity(BigDecimal.ZERO);
+            o.setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
+        });
+        when(taskService.validateTaskNotFinished(curTaskId)).thenReturn(curTask);
+        when(workOrderProcessService.getWorkOrderProcessListByWorkOrderId(workOrderId))
+                .thenReturn(buildTwoProcessChain(workOrderId, prevProcessId, curProcessId));
+        MesProTaskDO prevTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setWorkOrderId(workOrderId);
+            o.setProcessId(prevProcessId);
+            o.setQuantity(BigDecimal.valueOf(100));
+            o.setProducedQuantity(BigDecimal.valueOf(60));
+            o.setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
+        });
+        when(taskService.getTaskListByWorkOrderIds(Collections.singletonList(workOrderId)))
+                .thenReturn(List.of(prevTask));
+        when(processService.getProcess(prevProcessId)).thenReturn(
+                randomPojo(MesProProcessDO.class, o -> o.setName("CNC鍔犲伐")));
+
+        // 璋冪敤锛氬簲琚嫤鎴�
+        MesProFeedbackQuickCreateReqVO req = new MesProFeedbackQuickCreateReqVO();
+        req.setTaskId(curTaskId);
+        req.setFeedbackQuantity(BigDecimal.TEN);
+        ServiceException ex = assertThrows(ServiceException.class, () -> feedbackService.quickCreateFeedback(req));
+        assertTrue(ex.getMessage().contains("灏氭湭鎶ュ伐瀹屾垚"));
+        assertTrue(ex.getMessage().contains("CNC鍔犲伐"));
+        // 鏈湡姝g敓鎴愭姤宸ュ崟
+        verify(autoCodeRecordService, never()).generateAutoCode(anyString());
+    }
+
+    @Test
+    public void testQuickCreateFeedback_blockedWhenPreviousMultiTaskAnyNotFinished() {
+        // 鍑嗗锛氫笂閬撳伐搴忔湁 2 涓换鍔★紝涓�涓仛婊°�佷竴涓湭鍋氭弧锛屼粛搴旀嫤鎴�
+        Long workOrderId = randomLongId();
+        Long routeId = randomLongId();
+        Long prevProcessId = randomLongId();
+        Long curProcessId = randomLongId();
+        Long curTaskId = randomLongId();
+
+        MesProTaskDO curTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setId(curTaskId);
+            o.setWorkOrderId(workOrderId);
+            o.setRouteId(routeId);
+            o.setProcessId(curProcessId);
+            o.setQuantity(BigDecimal.valueOf(100));
+            o.setProducedQuantity(BigDecimal.ZERO);
+            o.setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
+        });
+        when(taskService.validateTaskNotFinished(curTaskId)).thenReturn(curTask);
+        when(workOrderProcessService.getWorkOrderProcessListByWorkOrderId(workOrderId))
+                .thenReturn(buildTwoProcessChain(workOrderId, prevProcessId, curProcessId));
+
+        MesProTaskDO finishedTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setWorkOrderId(workOrderId);
+            o.setProcessId(prevProcessId);
+            o.setQuantity(BigDecimal.valueOf(50));
+            o.setProducedQuantity(BigDecimal.valueOf(50)); // 宸插仛婊�
+            o.setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
+        });
+        MesProTaskDO partialTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setWorkOrderId(workOrderId);
+            o.setProcessId(prevProcessId);
+            o.setQuantity(BigDecimal.valueOf(50));
+            o.setProducedQuantity(BigDecimal.valueOf(20)); // 鏈仛婊�
+            o.setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
+        });
+        when(taskService.getTaskListByWorkOrderIds(Collections.singletonList(workOrderId)))
+                .thenReturn(ListUtil.of(finishedTask, partialTask));
+
+        MesProFeedbackQuickCreateReqVO req = new MesProFeedbackQuickCreateReqVO();
+        req.setTaskId(curTaskId);
+        req.setFeedbackQuantity(BigDecimal.TEN);
+        ServiceException ex = assertThrows(ServiceException.class, () -> feedbackService.quickCreateFeedback(req));
+        assertTrue(ex.getMessage().contains("灏氭湭鎶ュ伐瀹屾垚"));
+    }
+
+    @Test
+    public void testQuickCreateFeedback_allowedWhenPreviousProcessFinished() {
+        // 鍑嗗锛氫笂閬撳伐搴忎换鍔″凡鍋氭弧锛�100/100锛夛紝褰撳墠涓虹 2 閬撳伐搴忥紝搴旀斁琛�
+        Long workOrderId = randomLongId();
+        Long routeId = randomLongId();
+        Long prevProcessId = randomLongId();
+        Long curProcessId = randomLongId();
+        Long curTaskId = randomLongId();
+
+        MesProTaskDO curTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setId(curTaskId);
+            o.setWorkOrderId(workOrderId);
+            o.setRouteId(routeId);
+            o.setProcessId(curProcessId);
+            o.setQuantity(BigDecimal.valueOf(100));
+            o.setProducedQuantity(BigDecimal.ZERO);
+            o.setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
+        });
+        when(taskService.validateTaskNotFinished(curTaskId)).thenReturn(curTask);
+        when(workOrderProcessService.getWorkOrderProcessListByWorkOrderId(workOrderId))
+                .thenReturn(buildTwoProcessChain(workOrderId, prevProcessId, curProcessId));
+        MesProTaskDO prevTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setWorkOrderId(workOrderId);
+            o.setProcessId(prevProcessId);
+            o.setQuantity(BigDecimal.valueOf(100));
+            o.setProducedQuantity(BigDecimal.valueOf(100)); // 宸插仛婊�
+            o.setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
+        });
+        when(taskService.getTaskListByWorkOrderIds(Collections.singletonList(workOrderId)))
+                .thenReturn(List.of(prevTask));
+
+        // 璧伴�氬悗缁�昏緫鐨勬々
+        when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, curProcessId))
+                .thenReturn(MesProRouteProcessDO.builder().routeId(routeId).processId(curProcessId)
+                        .keyFlag(false).checkFlag(false).build());
+        when(autoCodeRecordService.generateAutoCode(anyString())).thenReturn("FB-ORDER-1");
+
+        // 璋冪敤锛氬簲鎴愬姛鐢熸垚鎶ュ伐鍗�
+        MesProFeedbackQuickCreateReqVO req = new MesProFeedbackQuickCreateReqVO();
+        req.setTaskId(curTaskId);
+        req.setFeedbackQuantity(BigDecimal.TEN);
+        MesProFeedbackQuickCreateRespVO resp = feedbackService.quickCreateFeedback(req);
+        assertNotNull(resp);
+        assertNotNull(resp.getFeedbackId());
+        verify(autoCodeRecordService).generateAutoCode(anyString());
+    }
+
+    @Test
+    public void testQuickCreateFeedback_allowedWhenPreviousProcessAllCancelled() {
+        // 鍑嗗锛氫笂閬撳伐搴忎换鍔″叏閮ㄥ彇娑堬紝瑙嗕负鏃犲墠缃姹傦紝褰撳墠绗� 2 閬撳伐搴忓簲鏀捐
+        Long workOrderId = randomLongId();
+        Long routeId = randomLongId();
+        Long prevProcessId = randomLongId();
+        Long curProcessId = randomLongId();
+        Long curTaskId = randomLongId();
+
+        MesProTaskDO curTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setId(curTaskId);
+            o.setWorkOrderId(workOrderId);
+            o.setRouteId(routeId);
+            o.setProcessId(curProcessId);
+            o.setQuantity(BigDecimal.valueOf(100));
+            o.setProducedQuantity(BigDecimal.ZERO);
+            o.setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
+        });
+        when(taskService.validateTaskNotFinished(curTaskId)).thenReturn(curTask);
+        when(workOrderProcessService.getWorkOrderProcessListByWorkOrderId(workOrderId))
+                .thenReturn(buildTwoProcessChain(workOrderId, prevProcessId, curProcessId));
+        MesProTaskDO cancelledTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setWorkOrderId(workOrderId);
+            o.setProcessId(prevProcessId);
+            o.setQuantity(BigDecimal.valueOf(100));
+            o.setProducedQuantity(BigDecimal.ZERO);
+            o.setStatus(MesProTaskStatusEnum.CANCELED.getStatus());
+        });
+        when(taskService.getTaskListByWorkOrderIds(Collections.singletonList(workOrderId)))
+                .thenReturn(List.of(cancelledTask));
+        when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, curProcessId))
+                .thenReturn(MesProRouteProcessDO.builder().routeId(routeId).processId(curProcessId)
+                        .keyFlag(false).checkFlag(false).build());
+        when(autoCodeRecordService.generateAutoCode(anyString())).thenReturn("FB-ORDER-2");
+
+        MesProFeedbackQuickCreateReqVO req = new MesProFeedbackQuickCreateReqVO();
+        req.setTaskId(curTaskId);
+        req.setFeedbackQuantity(BigDecimal.TEN);
+        MesProFeedbackQuickCreateRespVO resp = feedbackService.quickCreateFeedback(req);
+        assertNotNull(resp);
+        assertNotNull(resp.getFeedbackId());
+    }
+
+    @Test
+    public void testQuickCreateFeedback_allowedWhenCurrentIsFirstProcess() {
+        // 鍑嗗锛氬綋鍓嶄换鍔″氨鏄 1 閬撳伐搴忥紙閾句腑鍙湁涓�閬撳伐搴忥級锛屾棤鍓嶇疆锛屽簲鏀捐
+        Long workOrderId = randomLongId();
+        Long routeId = randomLongId();
+        Long curProcessId = randomLongId();
+        Long curTaskId = randomLongId();
+
+        MesProTaskDO curTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setId(curTaskId);
+            o.setWorkOrderId(workOrderId);
+            o.setRouteId(routeId);
+            o.setProcessId(curProcessId);
+            o.setQuantity(BigDecimal.valueOf(100));
+            o.setProducedQuantity(BigDecimal.ZERO);
+            o.setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
+        });
+        when(taskService.validateTaskNotFinished(curTaskId)).thenReturn(curTask);
+        when(workOrderProcessService.getWorkOrderProcessListByWorkOrderId(workOrderId))
+                .thenReturn(ListUtil.of(MesProWorkOrderProcessDO.builder()
+                        .workOrderId(workOrderId).processId(curProcessId).sort(1).build()));
+        when(routeProcessService.getRouteProcessByRouteIdAndProcessId(routeId, curProcessId))
+                .thenReturn(MesProRouteProcessDO.builder().routeId(routeId).processId(curProcessId)
+                        .keyFlag(false).checkFlag(false).build());
+        when(autoCodeRecordService.generateAutoCode(anyString())).thenReturn("FB-ORDER-3");
+
+        MesProFeedbackQuickCreateReqVO req = new MesProFeedbackQuickCreateReqVO();
+        req.setTaskId(curTaskId);
+        req.setFeedbackQuantity(BigDecimal.TEN);
+        MesProFeedbackQuickCreateRespVO resp = feedbackService.quickCreateFeedback(req);
+        assertNotNull(resp);
+        assertNotNull(resp.getFeedbackId());
+    }
+
+    @Test
+    public void testSubmitFeedback_blockedWhenPreviousProcessNotFinished() {
+        // 鍑嗗锛氬凡瀛樺湪鑽夌鎶ュ伐鍗曪紙绗� 2 閬撳伐搴忥級锛屼笂閬撳伐搴忔湭鍋氭弧锛屾彁浜ゅ簲琚嫤鎴�
+        Long workOrderId = randomLongId();
+        Long routeId = randomLongId();
+        Long prevProcessId = randomLongId();
+        Long curProcessId = randomLongId();
+        Long curTaskId = randomLongId();
+
+        MesProFeedbackDO feedback = randomPojo(MesProFeedbackDO.class, o -> {
+            o.setStatus(MesProFeedbackStatusEnum.PREPARE.getStatus());
+            o.setTaskId(curTaskId);
+            o.setWorkOrderId(workOrderId);
+            o.setRouteId(routeId);
+            o.setProcessId(curProcessId);
+        });
+        feedbackMapper.insert(feedback);
+
+        MesProTaskDO curTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setId(curTaskId);
+            o.setWorkOrderId(workOrderId);
+            o.setRouteId(routeId);
+            o.setProcessId(curProcessId);
+            o.setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
+        });
+        when(taskService.getTask(curTaskId)).thenReturn(curTask);
+        when(workOrderProcessService.getWorkOrderProcessListByWorkOrderId(workOrderId))
+                .thenReturn(buildTwoProcessChain(workOrderId, prevProcessId, curProcessId));
+        MesProTaskDO prevTask = randomPojo(MesProTaskDO.class, o -> {
+            o.setWorkOrderId(workOrderId);
+            o.setProcessId(prevProcessId);
+            o.setQuantity(BigDecimal.valueOf(100));
+            o.setProducedQuantity(BigDecimal.valueOf(30));
+            o.setStatus(MesProTaskStatusEnum.PREPARE.getStatus());
+        });
+        when(taskService.getTaskListByWorkOrderIds(Collections.singletonList(workOrderId)))
+                .thenReturn(List.of(prevTask));
+
+        MesProFeedbackSubmitReqVO req = new MesProFeedbackSubmitReqVO();
+        req.setId(feedback.getId());
+        ServiceException ex = assertThrows(ServiceException.class, () -> feedbackService.submitFeedback(req));
+        assertTrue(ex.getMessage().contains("灏氭湭鎶ュ伐瀹屾垚"));
+    }
+
 }

--
Gitblit v1.9.3