From 87733eb3623593da247c051593d64b8ff9e1d3a4 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 06 十一月 2026 19:00:39 +0800
Subject: [PATCH] feat(crm,mes): 扩展客户字段与重构批次号生成逻辑

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java |  910 +++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 799 insertions(+), 111 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
index 67f0fc5..f8ac385 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
@@ -5,12 +5,21 @@
 import cn.hutool.json.JSONUtil;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityBatchRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedReturnQualitySubmitReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
+import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.*;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnsales.MesWmReturnSalesLineDO;
+import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnSalesStatusEnum;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseAreaDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
@@ -31,6 +40,8 @@
 import cn.iocoder.yudao.module.mes.service.pro.batch.MesProBatchService;
 import cn.iocoder.yudao.module.mes.service.qc.approver.MesQcApproverService;
 import cn.iocoder.yudao.module.mes.service.wm.materialstockbag.MesWmMaterialStockBagService;
+import cn.iocoder.yudao.module.mes.service.wm.returnsales.MesWmReturnSalesLineService;
+import cn.iocoder.yudao.module.mes.service.wm.returnsales.MesWmReturnSalesService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
 import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseAreaService;
@@ -41,10 +52,15 @@
 import org.springframework.jdbc.BadSqlGrammarException;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.transaction.support.TransactionSynchronization;
+import org.springframework.transaction.support.TransactionSynchronizationManager;
 import org.springframework.validation.annotation.Validated;
 import java.math.BigDecimal;
+import java.sql.Timestamp;
 import java.time.LocalDateTime;
 import java.util.*;
+import java.util.function.Function;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
@@ -55,11 +71,23 @@
 
 @Service @Validated @Slf4j
 public class MesQcSeedQualityServiceImpl implements MesQcSeedQualityService {
-    private static final Set<String> REQUIRED = Set.of("purity", "cleanliness", "germination_rate", "moisture");
     private static final int MAX_BATCH_SIZE = 500, WRITE_BATCH_SIZE = 100;
     private static final int WAIT_RETEST = 10, WAIT_PROCESS = 20, WAIT_INITIAL_FAIL_REVIEW = 25, WAIT_COMPANY = 30,
             RELEASED = 40, WAIT_INITIAL_RECHECK = 50, WAIT_COMPANY_FAIL = 60, SCRAPPED = 70;
+    /** 璐ㄦ绾匡細1 鍔犲伐閮紱2 閿�鍞儴 */
+    private static final int QC_LINE_PROCESS = 1, QC_LINE_SALES = 2;
+    /** 涓氬姟绫诲瀷锛�1 鐢熶骇鍏ュ簱璐ㄦ锛�2 缁忛攢鍟嗛��璐ц川妫� */
+    private static final int BIZ_TYPE_PRODUCTION = 1, BIZ_TYPE_RETURN = 2;
+    /** 鏈嚎棰嗗瀹℃壒娴佺▼瀹氫箟鏍囪瘑 */
+    private static final String LINE_PROCESS_KEY = "mes_seed_quality_line_approve";
+    /** 鍏徃璐ㄦ瀹℃壒娴佺▼瀹氫箟鏍囪瘑 */
+    private static final String COMPANY_PROCESS_KEY = "mes_seed_quality_company_approve";
+    /** 缁忛攢鍟嗛��璐ц川妫�瀹℃壒娴佺▼瀹氫箟鏍囪瘑 */
+    private static final String RETURN_PROCESS_KEY = "mes_seed_return_approve";
     @Resource private MesQcSeedQualityMapper qualityMapper;
+    @Resource private BpmProcessInstanceApi processInstanceApi;
+    @Resource private MesWmReturnSalesService returnSalesService;
+    @Resource private MesWmReturnSalesLineService returnSalesLineService;
     @Resource private MesQcApproverService approverService;
     @Resource private MesQcSeedQualityActionMapper actionMapper;
     @Resource private MesQcSeedQualityIndicatorMapper indicatorMapper;
@@ -75,14 +103,93 @@
     @Resource private MesWmWarehouseService warehouseService;
     @Resource private MesWmWarehouseLocationService warehouseLocationService;
     @Resource private MesWmWarehouseAreaService warehouseAreaService;
+    @Resource private MesMdmItemService mdmItemService;
+    @Resource private MesQcSeedIndicatorRangeService rangeService;
 
     @Override
     public PageResult<MesQcSeedQualityDO> getQualityPage(MesQcSeedQualityPageReqVO reqVO) {
+        resolveBagScanCode(reqVO);
+        return qualityMapper.selectPage(reqVO);
+    }
+
+    @Override
+    public PageResult<MesQcSeedQualityBatchRespVO> getQualityBatchPage(MesQcSeedQualityPageReqVO reqVO) {
+        resolveBagScanCode(reqVO);
+        int pageNo = reqVO.getPageNo() == null || reqVO.getPageNo() < 1 ? 1 : reqVO.getPageNo();
+        int pageSize = reqVO.getPageSize() == null || reqVO.getPageSize() < 1 ? 10 : Math.min(reqVO.getPageSize(), 100);
+        long total = qualityMapper.countBatchGroups(reqVO);
+        if (total == 0) {
+            return new PageResult<>(Collections.emptyList(), 0L);
+        }
+        List<MesQcSeedQualityBatchRespVO> list = qualityMapper.selectBatchGroups(reqVO, (long) (pageNo - 1) * pageSize, pageSize)
+                .stream().map(this::toBatchRespVO).collect(Collectors.toList());
+        // 鍥炲~鎵规涓庡搧绉嶄俊鎭�
+        Set<Long> batchIds = list.stream().map(MesQcSeedQualityBatchRespVO::getBatchId)
+                .filter(Objects::nonNull).collect(Collectors.toSet());
+        Map<Long, MesProBatchDO> batchMap = batchIds.isEmpty() ? Collections.emptyMap()
+                : batchMapper.selectBatchIds(batchIds).stream()
+                .collect(Collectors.toMap(MesProBatchDO::getId, Function.identity(), (first, ignored) -> first));
+        Map<Long, MesMdmItemDO> itemMap = mdmItemService.getItemMap(list.stream()
+                .map(MesQcSeedQualityBatchRespVO::getItemId).filter(Objects::nonNull).collect(Collectors.toSet()));
+        for (MesQcSeedQualityBatchRespVO vo : list) {
+            vo.setBatchLabel(StrUtil.blankToDefault(vo.getBatchCode(), "鏈粦瀹氭壒娆�"));
+            MesProBatchDO batch = batchMap.get(vo.getBatchId());
+            if (batch != null) {
+                vo.setLineName(batch.getLineName());
+                vo.setProduceDate(batch.getProduceDate());
+            }
+            MesMdmItemDO item = itemMap.get(vo.getItemId());
+            if (item != null) {
+                vo.setItemCode(item.getCode());
+                vo.setItemName(item.getName());
+            }
+        }
+        return new PageResult<>(list, total);
+    }
+
+    private void resolveBagScanCode(MesQcSeedQualityPageReqVO reqVO) {
         if (StrUtil.isNotBlank(reqVO.getCode())) {
             MesProBagCodeDO bag = bagCodeMapper.selectByScanCode(reqVO.getCode());
-            if (bag != null) reqVO.setCode(bag.getCode());
+            if (bag != null) {
+                reqVO.setCode(bag.getCode());
+            }
         }
-        return qualityMapper.selectPage(reqVO);
+    }
+
+    private MesQcSeedQualityBatchRespVO toBatchRespVO(Map<String, Object> row) {
+        MesQcSeedQualityBatchRespVO vo = new MesQcSeedQualityBatchRespVO();
+        vo.setBatchCode((String) row.get("batchCode"));
+        vo.setBatchId(toLong(row.get("batchId")));
+        vo.setItemId(toLong(row.get("itemId")));
+        vo.setTotalCount(toInt(row.get("totalCount")));
+        vo.setPassCount(toInt(row.get("passCount")));
+        vo.setFailCount(toInt(row.get("failCount")));
+        vo.setProcessingCount(toInt(row.get("processingCount")));
+        vo.setPendingRetestCount(toInt(row.get("pendingRetestCount")));
+        vo.setPendingProcessReviewCount(toInt(row.get("pendingProcessReviewCount")));
+        vo.setPendingCompanyReviewCount(toInt(row.get("pendingCompanyReviewCount")));
+        vo.setReleasedCount(toInt(row.get("releasedCount")));
+        vo.setScrappedCount(toInt(row.get("scrappedCount")));
+        vo.setLastInspectedAt(toLocalDateTime(row.get("lastInspectedAt")));
+        return vo;
+    }
+
+    private static Long toLong(Object value) {
+        return value instanceof Number number ? number.longValue() : null;
+    }
+
+    private static Integer toInt(Object value) {
+        return value instanceof Number number ? number.intValue() : 0;
+    }
+
+    private static LocalDateTime toLocalDateTime(Object value) {
+        if (value instanceof LocalDateTime dateTime) {
+            return dateTime;
+        }
+        if (value instanceof Timestamp timestamp) {
+            return timestamp.toLocalDateTime();
+        }
+        return null;
     }
 
     @Override
@@ -95,9 +202,28 @@
         result.put("batchCode", bag.getBatchCode());
         result.put("inbound", bag.getInboundEventId() != null);
         result.put("canceled", Integer.valueOf(30).equals(bag.getStatus()));
-        MesQcSeedQualityDO quality = qualityMapper.selectByCode(bag.getCode());
+        Long itemId = bag.getItemId();
+        MesMdmItemDO item = itemId == null ? null : mdmItemService.getItem(itemId);
+        result.put("itemId", itemId);
+        result.put("itemCode", item == null ? null : item.getCode());
+        result.put("itemName", item == null ? null : item.getName());
+        result.put("indicators", toRangePayload(itemId));
+        List<MesQcSeedQualityDO> qualities = qualityMapper.selectListByCode(bag.getCode());
+        MesQcSeedQualityDO quality = qualities.stream()
+                .filter(q -> !isReturnQuality(q)).findFirst().orElse(null);
         result.put("qualityId", quality == null ? null : quality.getId());
         result.put("result", quality == null ? null : quality.getResult());
+        result.put("qualityIds", qualities.stream().map(MesQcSeedQualityDO::getId).toList());
+        result.put("qualityLines", qualities.stream()
+                .filter(q -> !isReturnQuality(q))
+                .map(q -> {
+                    Map<String, Object> line = new HashMap<>();
+                    line.put("id", q.getId());
+                    line.put("qcLine", q.getQcLine());
+                    line.put("workflowStatus", q.getWorkflowStatus());
+                    return line;
+                })
+                .toList());
         return result;
     }
 
@@ -127,9 +253,17 @@
 
     @Override
     public Map<String, Object> resolveScan(String code, String scanType) {
+        return resolveScan(code, scanType, QC_LINE_PROCESS);
+    }
+
+    @Override
+    public Map<String, Object> resolveScan(String code, String scanType, Integer qcLine) {
+        requireQcLine(qcLine);
         Scope scope = scope(code, scanType, false);
         List<MesProBagCodeDO> effective = scope.bags().stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus())).toList();
+        // 涓�娆℃彁浜ょ敓鎴愪袱鏉¤川妫�绾胯褰曪紝浠讳竴宸插瓨鍦ㄥ嵆瑙嗕负璇ヨ宸插畬鎴愬垵妫�銆�
         Set<String> inspected = qualityMapper.selectListByCodes(effective.stream().map(MesProBagCodeDO::getCode).toList()).stream()
+                .filter(q -> !isReturnQuality(q))
                 .map(MesQcSeedQualityDO::getCode).collect(java.util.stream.Collectors.toSet());
         Map<String, Object> result = new HashMap<>();
         result.put("scanType", scanType); result.put("code", scope.code()); result.put("businessCode", scope.businessCode());
@@ -138,9 +272,19 @@
         result.put("inbound", !effective.isEmpty() && effective.stream().allMatch(b -> b.getInboundEventId() != null));
         result.put("pendingBagIds", effective.stream().filter(b -> b.getInboundEventId() != null && !inspected.contains(b.getCode())).map(MesProBagCodeDO::getId).sorted().toList());
         result.put("completedBagIds", effective.stream().filter(b -> inspected.contains(b.getCode())).map(MesProBagCodeDO::getId).sorted().toList());
+        Long itemId = resolveItemId(scope);
+        MesMdmItemDO item = itemId == null ? null : mdmItemService.getItem(itemId);
+        result.put("itemId", itemId);
+        result.put("itemCode", item == null ? null : item.getCode());
+        result.put("itemName", item == null ? null : item.getName());
+        result.put("indicators", toRangePayload(itemId));
         if ("BAG".equals(scanType)) {
-            MesQcSeedQualityDO quality = qualityMapper.selectByCode(scope.code());
-            result.put("qualityId", quality == null ? null : quality.getId()); result.put("result", quality == null ? null : quality.getResult());
+            List<MesQcSeedQualityDO> qualities = qualityMapper.selectListByCode(scope.code());
+            MesQcSeedQualityDO quality = qualities.stream()
+                    .filter(q -> !isReturnQuality(q)).findFirst().orElse(null);
+            result.put("qualityId", quality == null ? null : quality.getId());
+            result.put("result", quality == null ? null : quality.getResult());
+            result.put("qualityIds", qualities.stream().map(MesQcSeedQualityDO::getId).toList());
         }
         return result;
     }
@@ -152,7 +296,9 @@
         if (scope.canceled()) throw invalidParamException("璐ㄦ瀵硅薄宸蹭綔搴�");
         List<MesProBagCodeDO> effective = scope.bags().stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus())).toList();
         if (effective.isEmpty()) throw new IllegalArgumentException("娌℃湁鏈夋晥琚嬬爜");
+        // 涓�娆℃彁浜ゅ悓鏃剁敓鎴愪袱鏉¤川妫�绾胯褰曪紝浠讳竴宸插瓨鍦ㄥ嵆瑙嗕负璇ヨ宸茶川妫�銆�
         Set<String> inspected = qualityMapper.selectListByCodes(effective.stream().map(MesProBagCodeDO::getCode).toList()).stream()
+                .filter(q -> !isReturnQuality(q))
                 .map(MesQcSeedQualityDO::getCode).collect(java.util.stream.Collectors.toSet());
         List<MesProBagCodeDO> pendingPreview = effective.stream().filter(b -> b.getInboundEventId() != null && !inspected.contains(b.getCode()))
                 .sorted(Comparator.comparing(MesProBagCodeDO::getId)).toList();
@@ -168,7 +314,8 @@
         Set<Long> currentIds = current.bags().stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus())).map(MesProBagCodeDO::getId).collect(java.util.stream.Collectors.toSet());
         if (!currentIds.containsAll(confirmed))
             throw new IllegalArgumentException("琚嬬爜褰掑睘宸插彉鍖栵紝璇烽噸鏂版壂鎻忔牳瀵�");
-        if (!qualityMapper.selectListByCodesForUpdate(locked.stream().map(MesProBagCodeDO::getCode).toList()).isEmpty())
+        if (qualityMapper.selectListByCodesForUpdate(locked.stream().map(MesProBagCodeDO::getCode).toList()).stream()
+                .anyMatch(q -> !isReturnQuality(q)))
             throw new IllegalArgumentException("寰呰川妫�娓呭崟宸插彉鍖栨垨宸插叏閮ㄨ川妫�锛岃閲嶆柊鎵弿锛岀姝㈤噸澶嶅鐞�");
         List<MesProBagCodeDO> pending = locked.stream().sorted(Comparator.comparing(MesProBagCodeDO::getId)).toList();
         submitInitialBatch(pending, req);
@@ -177,13 +324,14 @@
 
     private void submitInitialBatch(List<MesProBagCodeDO> bags,
                                     cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityGroupSubmitReqVO req) {
-        boolean passed = validateIndicators(req.getIndicators());
-        if (!passed && StrUtil.isBlank(req.getReason())) throw new IllegalArgumentException("璐ㄦ涓嶅悎鏍煎繀椤诲~鍐欏師鍥�");
         Long batchId = bags.get(0).getBatchId();
         if (batchId == null || bags.stream().anyMatch(bag -> !Objects.equals(batchId, bag.getBatchId())))
             throw new IllegalArgumentException("鎵归噺鍒濇鍙厑璁稿悓涓�鐢熶骇鎵规鐨勮鐮�");
         MesProBatchDO batch = proBatchService.getBatch(batchId);
         if (batch == null || batch.getItemId() == null) throw new IllegalArgumentException("鏈牴鎹鐮佽В鏋愬埌鐗╂枡淇℃伅");
+        Map<String, IndicatorMeta> expected = requireEffectiveIndicators(batch.getItemId());
+        boolean passed = validateIndicators(req.getIndicators(), expected);
+        if (!passed && StrUtil.isBlank(req.getReason())) throw new IllegalArgumentException("璐ㄦ涓嶅悎鏍煎繀椤诲~鍐欏師鍥�");
         if (Integer.valueOf(40).equals(batch.getStatus())) throw new IllegalArgumentException("鐢熶骇鎵规宸蹭綔搴燂紝鏃犳硶璐ㄦ");
         MesWmBatchDO wmBatch = mesWmBatchMapper.selectByCode(batch.getCode());
         if (wmBatch == null || materialStockMapper.selectTempStockCountByBatchId(wmBatch.getId()) == 0)
@@ -193,37 +341,48 @@
         if (qualifiedWarehouse == null) throw exception(WM_WAREHOUSE_NOT_EXISTS);
         if (!MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(qualifiedWarehouse.getType()))
             throw exception(WM_WAREHOUSE_TYPE_NOT_QUALIFIED);
-        // 鎻愬墠鏍¢獙鍚堟牸搴撳凡閰嶇疆搴撳尯/搴撲綅锛岄伩鍏嶅妫�瀹℃牳鏀捐鏃舵墠澶辫触
-        resolveQualifiedLocation(qualifiedWarehouse);
 
         LocalDateTime now = LocalDateTime.now();
         Long operatorId = getLoginUserId();
         int result = passed ? 1 : 2;
-        int status = passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW;
-        List<MesQcSeedQualityDO> qualities = bags.stream().map(bag -> MesQcSeedQualityDO.builder()
-                .code(bag.getCode()).itemId(batch.getItemId()).batchId(batch.getId()).batchCode(batch.getCode())
-                .result(result).reason(passed ? "" : req.getReason().trim()).workflowStatus(status)
-                .qualifiedWarehouseId(qualifiedWarehouse.getId()).inspectorId(operatorId).inspectedAt(now).build()).toList();
+        int status = passed ? WAIT_PROCESS : WAIT_RETEST;
+        // 涓�娆℃彁浜ゅ悓鏃剁敓鎴愬姞宸ラ儴涓庨攢鍞儴涓ゆ潯璁板綍锛屾寚鏍囦笌缁撹瀹屽叏涓�鑷淬��
+        List<MesQcSeedQualityDO> qualities = new ArrayList<>(bags.size() * 2);
+        for (MesProBagCodeDO bag : bags) {
+            for (int qcLine : new int[]{QC_LINE_PROCESS, QC_LINE_SALES}) {
+                qualities.add(MesQcSeedQualityDO.builder()
+                        .code(bag.getCode()).qcLine(qcLine).bizType(BIZ_TYPE_PRODUCTION)
+                        .itemId(batch.getItemId()).batchId(batch.getId()).batchCode(batch.getCode())
+                        .result(result).reason(passed ? "" : req.getReason().trim()).workflowStatus(status)
+                        .qualifiedWarehouseId(qualifiedWarehouse.getId()).inspectorId(operatorId).inspectedAt(now).build());
+            }
+        }
         qualityMapper.insertBatch(qualities, WRITE_BATCH_SIZE);
         if (qualities.stream().anyMatch(quality -> quality.getId() == null))
             throw new IllegalStateException("鎵归噺鍒濇璁板綍缂栧彿鐢熸垚澶辫触");
 
-        List<MesQcSeedQualityIndicatorDO> indicators = new ArrayList<>(qualities.size() * REQUIRED.size());
+        List<MesQcSeedQualityIndicatorDO> indicators = new ArrayList<>(qualities.size() * expected.size());
         List<MesQcSeedQualityActionDO> actions = new ArrayList<>(qualities.size());
         for (MesQcSeedQualityDO quality : qualities) {
             for (MesQcSeedQualitySubmitReqVO.Indicator item : req.getIndicators()) {
+                IndicatorMeta meta = expected.get(item.getCode());
                 indicators.add(MesQcSeedQualityIndicatorDO.builder().qualityId(quality.getId()).indicatorCode(item.getCode())
+                        .indicatorName(meta.name()).unit(meta.unit())
                         .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build());
             }
             actions.add(newAction(quality.getId(), "INITIAL", result, req.getReason(), req.getIndicators(), operatorId, now));
         }
-        indicatorMapper.insertBatch(indicators, WRITE_BATCH_SIZE * REQUIRED.size());
+        indicatorMapper.insertBatch(indicators, WRITE_BATCH_SIZE * expected.size());
         actionMapper.insertBatch(actions, WRITE_BATCH_SIZE);
+        String batchCode = batch.getCode();
+        if (passed) {
+            afterCommit(() -> startLineProcess(QC_LINE_PROCESS, batchCode, operatorId));
+            afterCommit(() -> startLineProcess(QC_LINE_SALES, batchCode, operatorId));
+        }
     }
 
     @Override @Transactional(rollbackFor = Exception.class)
     public Long submit(MesQcSeedQualitySubmitReqVO reqVO) {
-        if (reqVO.getIndicators().size() != 4) throw new IllegalArgumentException("蹇呴』鎻愪氦鍥涢」鎸囨爣");
         // 绉嶅瓙璐ㄦ蹇呴』浠ヨ鐮侊紙鍗曡锛夋彁浜わ紝鐗╂枡淇℃伅鐢辫鐮佽嚜鍔ㄨВ鏋愶紝鍓嶇涓嶅啀鍗曠嫭閫夋嫨鐗╂枡
         MesProBagCodeDO bag = bagCodeMapper.selectByScanCodeForUpdate(reqVO.getCode());
         if (bag == null) {
@@ -232,7 +391,9 @@
         if (Integer.valueOf(30).equals(bag.getStatus()) || bag.getInboundEventId() == null)
             throw new IllegalArgumentException("琚嬬爜宸蹭綔搴熸垨灏氭湭鍏ュ簱锛屾棤娉曡川妫�");
         // 鍙岀爜閿佸畾鍚屼竴琚嬶紝骞朵互杩芥函鐮佽褰曠粨鏋滐紝闃叉鎹㈢爜鎴栧苟鍙戦噸澶嶆墸鍑忓簱瀛樸��
-        if (qualityMapper.selectByCodeForUpdate(bag.getCode()) != null) throw new IllegalArgumentException("璇ヨ鐮佸凡璐ㄦ锛岀姝㈤噸澶嶆彁浜�");
+        if (qualityMapper.selectListByCodeForUpdate(bag.getCode()).stream()
+                .anyMatch(q -> !isReturnQuality(q)))
+            throw new IllegalArgumentException("璇ヨ鐮佸凡璐ㄦ锛岀姝㈤噸澶嶆彁浜�");
         MesProBatchDO batch = bag.getBatchId() == null ? null : proBatchService.getBatch(bag.getBatchId());
         if (batch == null || batch.getItemId() == null) {
             throw new IllegalArgumentException("鏈牴鎹鐮佽В鏋愬埌鐗╂枡淇℃伅锛岃纭鎵爜鍐呭鎴栨壒娆″彿姝g‘");
@@ -254,35 +415,39 @@
         if (!MesWmWarehouseTypeEnum.QUALIFIED.getType().equals(qualifiedWarehouse.getType())) {
             throw exception(WM_WAREHOUSE_TYPE_NOT_QUALIFIED);
         }
-        // 鎻愬墠鏍¢獙鍚堟牸搴撳凡閰嶇疆搴撳尯/搴撲綅锛岄伩鍏嶅妫�瀹℃牳鏀捐鏃舵墠澶辫触
-        resolveQualifiedLocation(qualifiedWarehouse);
-        Set<String> codes = new HashSet<>();
-        boolean passed = true;
-        MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(bag.getCode()).itemId(batch.getItemId())
-                .batchCode(batch.getCode()).batchId(batch.getId()).reason(reqVO.getReason()).result(0)
-                .qualifiedWarehouseId(qualifiedWarehouse.getId()).inspectorId(getLoginUserId())
-                .inspectedAt(LocalDateTime.now()).build();
-        qualityMapper.insert(quality);
-        for (MesQcSeedQualitySubmitReqVO.Indicator item : reqVO.getIndicators()) {
-            if (item == null || item.getCode() == null || item.getValue() == null || item.getMinValue() == null || item.getMaxValue() == null)
-                throw new IllegalArgumentException("鍥涢」鎸囨爣鐨勫疄娴嬪�煎拰鏈鍚堟牸鑼冨洿蹇呴』瀹屾暣");
-            if (!REQUIRED.contains(item.getCode()) || !codes.add(item.getCode())) throw new IllegalArgumentException("鎸囨爣蹇呴』涓虹函搴︺�佸噣搴︺�佸彂鑺界巼銆佹按鍒嗕笖涓嶅彲閲嶅");
-            if (item.getMinValue().compareTo(item.getMaxValue()) > 0) throw new IllegalArgumentException("鎸囨爣涓嬮檺涓嶈兘澶т簬涓婇檺");
-            if (item.getValue().compareTo(BigDecimal.ZERO) < 0 || item.getValue().compareTo(new BigDecimal("100")) > 0
-                    || item.getMinValue().compareTo(BigDecimal.ZERO) < 0 || item.getMaxValue().compareTo(new BigDecimal("100")) > 0)
-                throw new IllegalArgumentException("鐧惧垎姣旀寚鏍囧繀椤诲浜� 0鈥�100");
-            if (item.getValue().compareTo(item.getMinValue()) < 0 || item.getValue().compareTo(item.getMaxValue()) > 0) passed = false;
-            indicatorMapper.insert(MesQcSeedQualityIndicatorDO.builder().qualityId(quality.getId()).indicatorCode(item.getCode())
-                    .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build());
-        }
-        if (!codes.equals(REQUIRED)) throw new IllegalArgumentException("鍥涢」鎸囨爣涓嶅畬鏁�");
+        Map<String, IndicatorMeta> expected = requireEffectiveIndicators(batch.getItemId());
+        boolean passed = validateIndicators(reqVO.getIndicators(), expected);
         if (!passed && StrUtil.isBlank(reqVO.getReason())) throw new IllegalArgumentException("璐ㄦ涓嶅悎鏍煎繀椤诲~鍐欏師鍥�");
-        quality.setResult(passed ? 1 : 2);
-        quality.setWorkflowStatus(passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW);
-        qualityMapper.updateById(quality);
-        recordAction(quality.getId(), "INITIAL", quality.getResult(), reqVO.getReason(), reqVO.getIndicators());
+        LocalDateTime now = LocalDateTime.now();
+        Long operatorId = getLoginUserId();
+        int result = passed ? 1 : 2;
+        int status = passed ? WAIT_PROCESS : WAIT_RETEST;
+        // 涓�娆℃彁浜ゅ悓鏃剁敓鎴愬姞宸ラ儴涓庨攢鍞儴涓ゆ潯璁板綍锛屾寚鏍囦笌缁撹瀹屽叏涓�鑷淬��
+        Long firstQualityId = null;
+        for (int qcLine : new int[]{QC_LINE_PROCESS, QC_LINE_SALES}) {
+            MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(bag.getCode())
+                    .qcLine(qcLine).bizType(BIZ_TYPE_PRODUCTION)
+                    .itemId(batch.getItemId()).batchCode(batch.getCode()).batchId(batch.getId())
+                    .reason(passed ? "" : reqVO.getReason().trim()).result(result).workflowStatus(status)
+                    .qualifiedWarehouseId(qualifiedWarehouse.getId()).inspectorId(operatorId)
+                    .inspectedAt(now).build();
+            qualityMapper.insert(quality);
+            for (MesQcSeedQualitySubmitReqVO.Indicator item : reqVO.getIndicators()) {
+                IndicatorMeta meta = expected.get(item.getCode());
+                indicatorMapper.insert(MesQcSeedQualityIndicatorDO.builder().qualityId(quality.getId()).indicatorCode(item.getCode())
+                        .indicatorName(meta.name()).unit(meta.unit())
+                        .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build());
+            }
+            recordAction(quality.getId(), "INITIAL", result, reqVO.getReason(), reqVO.getIndicators());
+            if (firstQualityId == null) firstQualityId = quality.getId();
+        }
         // 鍒濇浠呰褰曠粨璁猴紱琚嬩粛鐣欏湪鏆傚瓨搴擄紝寰呭垵妫�瀹℃牳銆佸妫�鍙婃渶缁堝鏍稿悗鍐嶆斁琛屾垨鎶ュ簾銆�
-        return quality.getId();
+        String batchCode = batch.getCode();
+        if (passed) {
+            afterCommit(() -> startLineProcess(QC_LINE_PROCESS, batchCode, operatorId));
+            afterCommit(() -> startLineProcess(QC_LINE_SALES, batchCode, operatorId));
+        }
+        return firstQualityId;
     }
 
     private void finalizeStock(MesQcSeedQualityDO quality, boolean passed) {
@@ -370,25 +535,47 @@
         MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id);
         if (quality == null || !Set.of(WAIT_RETEST, WAIT_INITIAL_RECHECK).contains(quality.getWorkflowStatus()))
             throw new IllegalArgumentException("褰撳墠鐘舵�佷笉鑳芥彁浜ゅ妫�锛岃鍒锋柊鍚庨噸璇�");
+        boolean returnQuality = Objects.equals(quality.getBizType(), BIZ_TYPE_RETURN);
+        if (returnQuality && !Objects.equals(quality.getWorkflowStatus(), WAIT_RETEST))
+            throw new IllegalArgumentException("缁忛攢鍟嗛��璐ц川妫�褰撳墠鐘舵�佷笉鑳藉妫�锛岃鍒锋柊鍚庨噸璇�");
         boolean initialRecheck = Objects.equals(quality.getWorkflowStatus(), WAIT_INITIAL_RECHECK);
         if (!Objects.equals(quality.getCode(), req.getCode())) throw new IllegalArgumentException("妫�楠岃鐮佷笌鍘熻川妫�璁板綍涓嶄竴鑷�");
-        boolean passed = validateIndicators(req.getIndicators());
-        if (!passed && StrUtil.isBlank(req.getReason())) throw new IllegalArgumentException("妫�楠屼笉鍚堟牸蹇呴』濉啓鍘熷洜");
-        recordAction(id, initialRecheck ? "INITIAL_RECHECK" : "RETEST", passed ? 1 : 2, req.getReason(), req.getIndicators());
-        MesQcSeedQualityDO update = MesQcSeedQualityDO.builder().id(id).result(passed ? 1 : 2)
-                .reason(passed ? "" : req.getReason().trim())
-                .inspectorId(getLoginUserId()).inspectedAt(LocalDateTime.now())
-                .workflowStatus(initialRecheck ? (passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW)
-                        : (passed ? WAIT_COMPANY : WAIT_COMPANY_FAIL)).build();
-        if (!initialRecheck) {
-            update.setRetestResult(passed ? 1 : 2);
-            update.setRetestedAt(LocalDateTime.now());
-        }
-        qualityMapper.updateById(update);
-        // 椤甸潰銆佸叕寮�杩芥函璇诲彇涓绘寚鏍囪〃锛涘悓姝ヤ负鏈�鏂颁竴娆℃祴鍊硷紝鍘嗘娴嬪�煎湪鎿嶄綔鐣欑棔琛ㄤ繚鐣欍��
+        // 澶嶆娌跨敤鍘熻褰曠殑鎸囨爣闆嗗悎锛岄伩鍏嶉厤缃彉鏇村鑷村巻鍙茶褰曟棤娉曞妫�
         Map<String, MesQcSeedQualityIndicatorDO> existing = new HashMap<>();
         for (MesQcSeedQualityIndicatorDO indicator : indicatorMapper.selectListByQualityId(id))
             existing.put(indicator.getIndicatorCode(), indicator);
+        boolean passed = validateIndicators(req.getIndicators(), toMetaMap(existing.values(), quality.getItemId()));
+        if (!passed && StrUtil.isBlank(req.getReason())) throw new IllegalArgumentException("妫�楠屼笉鍚堟牸蹇呴』濉啓鍘熷洜");
+        recordAction(id, initialRecheck ? "INITIAL_RECHECK" : "RETEST", passed ? 1 : 2, req.getReason(), req.getIndicators());
+        Long operatorId = getLoginUserId();
+        LocalDateTime now = LocalDateTime.now();
+        int nextStatus;
+        if (returnQuality) {
+            // 澶嶆缁撹缁熶竴浜ょ敱閿�鍞儴棰嗗瀹℃壒锛屼笉鍚堟牸鏃跺鎵瑰悗涓嶄簣閫�璐с��
+            nextStatus = WAIT_PROCESS;
+        } else if (initialRecheck) {
+            nextStatus = passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW;
+        } else {
+            nextStatus = passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW;
+        }
+        MesQcSeedQualityDO update = MesQcSeedQualityDO.builder().id(id).result(passed ? 1 : 2)
+                .reason(passed ? "" : req.getReason().trim())
+                .inspectorId(operatorId).inspectedAt(now)
+                .workflowStatus(nextStatus).build();
+        if (!initialRecheck) {
+            update.setRetestResult(passed ? 1 : 2);
+            update.setRetestedAt(now);
+        }
+        qualityMapper.updateById(update);
+        if (returnQuality) {
+            afterCommit(() -> startReturnProcess(quality, operatorId));
+        } else if (initialRecheck) {
+            afterCommit(() -> startLineProcess(quality.getQcLine(), quality.getBatchCode(), operatorId));
+        } else if (!initialRecheck) {
+            // 鍒濇涓嶅悎鏍肩殑澶嶆瀹屾垚鍚庯紝鍏堜氦鏈嚎閮ㄩ棬瀹℃壒銆�
+            afterCommit(() -> startLineProcess(quality.getQcLine(), quality.getBatchCode(), operatorId));
+        }
+        // 椤甸潰銆佸叕寮�杩芥函璇诲彇涓绘寚鏍囪〃锛涘悓姝ヤ负鏈�鏂颁竴娆℃祴鍊硷紝鍘嗘娴嬪�煎湪鎿嶄綔鐣欑棔琛ㄤ繚鐣欍��
         for (MesQcSeedQualitySubmitReqVO.Indicator item : req.getIndicators()) {
             MesQcSeedQualityIndicatorDO indicator = existing.get(item.getCode());
             if (indicator == null) throw new IllegalArgumentException("璐ㄦ鎸囨爣璁板綍缂哄け");
@@ -404,6 +591,7 @@
         validateLockedIds(ids, qualities);
         String batchCode = null;
         Integer workflowStatus = null;
+        Integer batchQcLine = null;
         boolean first = true;
         for (MesQcSeedQualityDO quality : qualities) {
             if (!Set.of(WAIT_RETEST, WAIT_INITIAL_RECHECK).contains(quality.getWorkflowStatus()))
@@ -413,9 +601,12 @@
             if (first) {
                 batchCode = quality.getBatchCode();
                 workflowStatus = quality.getWorkflowStatus();
+                batchQcLine = quality.getQcLine();
                 first = false;
-            } else if (!Objects.equals(batchCode, quality.getBatchCode()) || !Objects.equals(workflowStatus, quality.getWorkflowStatus())) {
-                throw new IllegalArgumentException("鎵归噺璐ㄦ鍙厑璁稿悓鐢熶骇鎵规銆佸悓娴佺▼鐘舵�佺殑璁板綍");
+            } else if (!Objects.equals(batchCode, quality.getBatchCode())
+                    || !Objects.equals(workflowStatus, quality.getWorkflowStatus())
+                    || !Objects.equals(batchQcLine, quality.getQcLine())) {
+                throw new IllegalArgumentException("鎵归噺璐ㄦ鍙厑璁稿悓鐢熶骇鎵规銆佸悓璐ㄦ绾垮拰鍚屾祦绋嬬姸鎬佺殑璁板綍");
             }
         }
         applyRetestBatch(qualities, req.getIndicators(), req.getReason());
@@ -428,12 +619,19 @@
 
     @Override
     public Map<String, Object> resolveRetestScope(String code, String scanType) {
+        return resolveRetestScope(code, scanType, QC_LINE_PROCESS);
+    }
+
+    @Override
+    public Map<String, Object> resolveRetestScope(String code, String scanType, Integer qcLine) {
+        requireQcLine(qcLine);
         checkRetestScanType(scanType);
         Scope scope = scope(code, scanType, false);
         if (scope.canceled()) throw invalidParamException("璐ㄦ瀵硅薄宸蹭綔搴�");
         List<String> codes = scope.bags().stream().filter(b -> !Integer.valueOf(30).equals(b.getStatus()) && b.getInboundEventId() != null)
                 .map(MesProBagCodeDO::getCode).toList();
-        List<MesQcSeedQualityDO> qualities = codes.isEmpty() ? List.of() : qualityMapper.selectListByCodes(codes);
+        List<MesQcSeedQualityDO> qualities = codes.isEmpty() ? List.of() : qualityMapper.selectListByCodes(codes).stream()
+                .filter(q -> Objects.equals(q.getQcLine(), qcLine) && !isReturnQuality(q)).toList();
         List<Long> retestIds = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), WAIT_RETEST))
                 .map(MesQcSeedQualityDO::getId).sorted().toList();
         List<Long> initialRecheckIds = qualities.stream().filter(q -> Objects.equals(q.getWorkflowStatus(), WAIT_INITIAL_RECHECK))
@@ -454,11 +652,19 @@
         result.put("pendingCompanyIds", pendingCompanyIds); result.put("pendingCompanyFailIds", pendingCompanyFailIds);
         result.put("pendingProcessCount", pendingProcessIds.size() + pendingInitialFailIds.size());
         result.put("pendingCompanyCount", pendingCompanyIds.size() + pendingCompanyFailIds.size());
+        Long itemId = qualities.stream().map(MesQcSeedQualityDO::getItemId).filter(Objects::nonNull).findFirst().orElse(null);
+        result.put("itemId", itemId);
+        MesQcSeedQualityDO actionable = qualities.stream()
+                .filter(q -> Set.of(WAIT_RETEST, WAIT_INITIAL_RECHECK).contains(q.getWorkflowStatus()))
+                .findFirst().orElse(null);
+        result.put("indicators", actionable == null ? List.of()
+                : toIndicatorPayload(indicatorMapper.selectListByQualityId(actionable.getId()), itemId));
         return result;
     }
 
     @Override @Transactional(rollbackFor = Exception.class)
     public Map<String, Object> retestScope(cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualityScopeRetestReqVO req) {
+        requireQcLine(req.getQcLine());
         checkRetestScanType(req.getScanType());
         if (!Set.of(WAIT_RETEST, WAIT_INITIAL_RECHECK).contains(req.getWorkflowStatus()))
             throw new IllegalArgumentException("鎵归噺璐ㄦ鐘舵�佹棤鏁�");
@@ -470,7 +676,8 @@
         if (effective.isEmpty()) throw new IllegalArgumentException("褰撳墠鑼冨洿娌℃湁宸插叆搴撶殑鏈夋晥琚嬬爜");
         Set<String> effectiveCodes = effective.stream().map(MesProBagCodeDO::getCode).collect(java.util.stream.Collectors.toSet());
         List<MesQcSeedQualityDO> pendingPreview = qualityMapper.selectListByCodes(effectiveCodes).stream()
-                .filter(q -> Objects.equals(q.getWorkflowStatus(), req.getWorkflowStatus()))
+                .filter(q -> Objects.equals(q.getQcLine(), req.getQcLine())
+                        && !isReturnQuality(q) && Objects.equals(q.getWorkflowStatus(), req.getWorkflowStatus()))
                 .sorted(Comparator.comparing(MesQcSeedQualityDO::getId)).toList();
         List<Long> expectedChunk = pendingPreview.stream().limit(MAX_BATCH_SIZE).map(MesQcSeedQualityDO::getId).toList();
         if (expectedChunk.isEmpty() || !confirmed.equals(expectedChunk))
@@ -478,6 +685,7 @@
         List<MesQcSeedQualityDO> pending = qualityMapper.selectListByIdsForUpdate(confirmed);
         validateLockedIds(confirmed, pending);
         if (pending.stream().anyMatch(quality -> !Objects.equals(quality.getWorkflowStatus(), req.getWorkflowStatus())
+                || !Objects.equals(quality.getQcLine(), req.getQcLine()) || isReturnQuality(quality)
                 || !effectiveCodes.contains(quality.getCode())))
             throw new IllegalArgumentException("寰呰川妫�璁板綍鐘舵�佹垨褰掑睘宸插彉鍖栵紝璇烽噸鏂版壂鎻忔牳瀵�");
         applyRetestBatch(pending, req.getIndicators(), req.getReason());
@@ -486,29 +694,48 @@
 
     private void applyRetestBatch(List<MesQcSeedQualityDO> qualities,
                                   List<MesQcSeedQualitySubmitReqVO.Indicator> submittedIndicators, String reason) {
-        boolean passed = validateIndicators(submittedIndicators);
-        if (!passed && StrUtil.isBlank(reason)) throw new IllegalArgumentException("妫�楠屼笉鍚堟牸蹇呴』濉啓鍘熷洜");
         List<Long> ids = qualities.stream().map(MesQcSeedQualityDO::getId).toList();
         Map<Long, Map<String, MesQcSeedQualityIndicatorDO>> existingByQuality = new HashMap<>();
         for (MesQcSeedQualityIndicatorDO indicator : indicatorMapper.selectListByQualityIds(ids)) {
             existingByQuality.computeIfAbsent(indicator.getQualityId(), ignored -> new HashMap<>())
                     .put(indicator.getIndicatorCode(), indicator);
         }
+        Map<String, MesQcSeedQualityIndicatorDO> firstIndicators = existingByQuality.get(qualities.get(0).getId());
+        Map<String, IndicatorMeta> expected = toMetaMap(
+                firstIndicators == null ? Collections.<MesQcSeedQualityIndicatorDO>emptyList() : firstIndicators.values(),
+                qualities.get(0).getItemId());
+        boolean passed = validateIndicators(submittedIndicators, expected);
+        if (!passed && StrUtil.isBlank(reason)) throw new IllegalArgumentException("妫�楠屼笉鍚堟牸蹇呴』濉啓鍘熷洜");
 
         LocalDateTime now = LocalDateTime.now();
         Long operatorId = getLoginUserId();
         int result = passed ? 1 : 2;
         List<MesQcSeedQualityDO> qualityUpdates = new ArrayList<>(qualities.size());
-        List<MesQcSeedQualityIndicatorDO> indicatorUpdates = new ArrayList<>(qualities.size() * REQUIRED.size());
+        List<MesQcSeedQualityIndicatorDO> indicatorUpdates = new ArrayList<>(qualities.size() * submittedIndicators.size());
         List<MesQcSeedQualityActionDO> actions = new ArrayList<>(qualities.size());
+        Set<String> lineProcessTargets = new HashSet<>();
+        List<MesQcSeedQualityDO> returnReady = new ArrayList<>();
         for (MesQcSeedQualityDO quality : qualities) {
+            boolean returnQuality = Objects.equals(quality.getBizType(), BIZ_TYPE_RETURN);
             boolean initialRecheck = Objects.equals(quality.getWorkflowStatus(), WAIT_INITIAL_RECHECK);
-            if (!initialRecheck && !Objects.equals(quality.getWorkflowStatus(), WAIT_RETEST))
+            if (returnQuality && !Objects.equals(quality.getWorkflowStatus(), WAIT_RETEST))
                 throw new IllegalArgumentException("鎵�閫夎褰曞寘鍚笉鍙楠岀殑鐘舵�侊紝璇峰埛鏂板悗閲嶈瘯");
+            if (!returnQuality && !initialRecheck && !Objects.equals(quality.getWorkflowStatus(), WAIT_RETEST))
+                throw new IllegalArgumentException("鎵�閫夎褰曞寘鍚笉鍙楠岀殑鐘舵�侊紝璇峰埛鏂板悗閲嶈瘯");
+            int nextStatus;
+            if (returnQuality) {
+                nextStatus = WAIT_PROCESS;
+                returnReady.add(quality);
+            } else if (initialRecheck) {
+                nextStatus = passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW;
+                lineProcessTargets.add(quality.getQcLine() + "#" + quality.getBatchCode());
+            } else {
+                nextStatus = passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW;
+                lineProcessTargets.add(quality.getQcLine() + "#" + quality.getBatchCode());
+            }
             MesQcSeedQualityDO update = MesQcSeedQualityDO.builder().id(quality.getId()).result(result)
                     .reason(passed ? "" : reason.trim()).inspectorId(operatorId).inspectedAt(now)
-                    .workflowStatus(initialRecheck ? (passed ? WAIT_PROCESS : WAIT_INITIAL_FAIL_REVIEW)
-                            : (passed ? WAIT_COMPANY : WAIT_COMPANY_FAIL)).build();
+                    .workflowStatus(nextStatus).build();
             if (!initialRecheck) {
                 update.setRetestResult(result);
                 update.setRetestedAt(now);
@@ -525,8 +752,14 @@
                     reason, submittedIndicators, operatorId, now));
         }
         qualityMapper.updateBatch(qualityUpdates, WRITE_BATCH_SIZE);
-        indicatorMapper.updateBatch(indicatorUpdates, WRITE_BATCH_SIZE * REQUIRED.size());
+        indicatorMapper.updateBatch(indicatorUpdates, WRITE_BATCH_SIZE * submittedIndicators.size());
         actionMapper.insertBatch(actions, WRITE_BATCH_SIZE);
+        // 澶嶆鎸夎褰曟墍灞炶川妫�绾垮垎鍒彂璧锋湰绾垮鎵癸紝閬垮厤鎻愬墠瑙﹀彂鍙︿竴鏉$嚎銆�
+        lineProcessTargets.forEach(target -> {
+            String[] parts = target.split("#", 2);
+            afterCommit(() -> startLineProcess(Integer.valueOf(parts[0]), parts[1], operatorId));
+        });
+        returnReady.forEach(quality -> afterCommit(() -> startReturnProcess(quality, operatorId)));
     }
 
     @Override @Transactional(rollbackFor = Exception.class)
@@ -534,28 +767,84 @@
         MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id);
         if (quality == null || !Set.of(WAIT_PROCESS, WAIT_INITIAL_FAIL_REVIEW).contains(quality.getWorkflowStatus()))
             throw new IllegalArgumentException("褰撳墠鐘舵�佷笉鑳借繘琛屽垵妫�瀹℃牳");
-        approverService.validateApprover(MesQcApprovalTypeEnum.SEED_QUALITY.getType(),
-                MesQcApproveNodeEnum.PROCESS.getNode(), getLoginUserId());
+        requireLegacyApproval(quality);
+        applyProcessApproval(quality, approved, opinion, true);
+    }
+
+    /**
+     * 鏈嚎棰嗗瀹℃壒锛氱敓浜ц川妫�閫氳繃鍚庤繘鍏ュ叕鍙歌川妫�瀹℃壒锛岄┏鍥炲悗閲嶆柊鍒濇锛涚粡閿�鍟嗛��璐ц川妫�鍒欑洿鎺ョ粓瀹°��
+     */
+    private void applyProcessApproval(MesQcSeedQualityDO quality, boolean approved, String opinion, boolean validateApprover) {
+        if (validateApprover) {
+            approverService.validateApprover(MesQcApprovalTypeEnum.SEED_QUALITY.getType(),
+                    MesQcApproveNodeEnum.PROCESS.getNode(), getLoginUserId());
+        }
         if (!approved && StrUtil.isBlank(opinion)) throw new IllegalArgumentException("椹冲洖鏃惰濉啓瀹℃壒鎰忚");
-        int next = approved ? WAIT_RETEST : WAIT_INITIAL_RECHECK;
-        qualityMapper.updateById(MesQcSeedQualityDO.builder().id(id).workflowStatus(next)
-                .processApprovedAt(LocalDateTime.now()).build());
-        recordAction(id, approved ? "PROCESS_APPROVE" : "PROCESS_REJECT", quality.getResult(), opinion, null);
+        LocalDateTime now = LocalDateTime.now();
+        Long operatorId = getLoginUserId();
+        if (Objects.equals(quality.getBizType(), BIZ_TYPE_RETURN)) {
+            if (!Objects.equals(quality.getWorkflowStatus(), WAIT_PROCESS))
+                throw new IllegalArgumentException("缁忛攢鍟嗛��璐ц川妫�褰撳墠鐘舵�佷笉鑳借繘琛岄攢鍞儴瀹℃壒");
+            applyReturnApproval(quality, approved, opinion, operatorId, now);
+            return;
+        }
+        int next = approved ? (Objects.equals(quality.getQcLine(), 0) ? WAIT_RETEST
+                : Objects.equals(quality.getResult(), 1) ? WAIT_COMPANY : WAIT_COMPANY_FAIL)
+                : WAIT_INITIAL_RECHECK;
+        qualityMapper.updateById(MesQcSeedQualityDO.builder().id(quality.getId()).workflowStatus(next)
+                .processApprovedAt(now).build());
+        if (!approved) qualityMapper.clearLineProcessInstance(quality.getId());
+        actionMapper.insert(newAction(quality.getId(), approved ? "PROCESS_APPROVE" : "PROCESS_REJECT",
+                quality.getResult(), opinion, null, operatorId, now));
+        if (approved && !Objects.equals(quality.getQcLine(), 0))
+            afterCommit(() -> maybeStartCompanyProcess(quality.getBatchCode(), operatorId));
     }
 
     @Override @Transactional(rollbackFor = Exception.class)
     public void companyApprove(Long id, boolean approved, String opinion) {
         MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id);
-        if (quality == null || !Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL).contains(quality.getWorkflowStatus()))
+        if (quality == null) throw new IllegalArgumentException("璐ㄦ璁板綍涓嶅瓨鍦�");
+        if (Objects.equals(quality.getBizType(), BIZ_TYPE_RETURN))
+            throw new IllegalArgumentException("缁忛攢鍟嗛��璐ц川妫�璇蜂娇鐢ㄩ攢鍞儴瀹℃壒");
+        if (!Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL).contains(quality.getWorkflowStatus()))
             throw new IllegalArgumentException("褰撳墠鐘舵�佷笉鑳借繘琛屽妫�瀹℃牳");
+        requireLegacyApproval(quality);
         approverService.validateApprover(MesQcApprovalTypeEnum.SEED_QUALITY.getType(),
                 MesQcApproveNodeEnum.COMPANY.getNode(), getLoginUserId());
+        applyCompanyApproval(quality.getCode(), approved, opinion, getLoginUserId());
+    }
+
+    /**
+     * 鍏徃璐ㄦ瀹℃壒锛氫互銆岃銆嶄负鍗曚綅鎴愬瀹℃壒锛屼袱鏉¤川妫�绾块兘璧板畬鎵嶅厑璁稿鎵广��
+     * 涓ゆ潯绾垮潎鍚堟牸鎵嶆斁琛屽叆搴擄紝浠讳竴鏉$嚎涓嶅悎鏍煎垯鏁磋鎶ュ簾锛涢┏鍥炲垯涓ゆ潯绾垮叏閮ㄩ��鍥炲妫�銆�
+     */
+    private void applyCompanyApproval(String code, boolean approved, String opinion, Long operatorId) {
         if (!approved && StrUtil.isBlank(opinion)) throw new IllegalArgumentException("椹冲洖鏃惰濉啓瀹℃壒鎰忚");
-        if (approved) finalizeStock(quality, quality.getWorkflowStatus() == WAIT_COMPANY);
-        int next = approved ? (quality.getWorkflowStatus() == WAIT_COMPANY ? RELEASED : SCRAPPED) : WAIT_RETEST;
-        qualityMapper.updateById(MesQcSeedQualityDO.builder().id(id).workflowStatus(next)
-                .companyApprovedAt(LocalDateTime.now()).build());
-        recordAction(id, approved ? "COMPANY_APPROVE" : "COMPANY_REJECT", quality.getResult(), opinion, null);
+        List<MesQcSeedQualityDO> pair = qualityMapper.selectListByCodeForUpdate(code).stream()
+                .filter(q -> !isReturnQuality(q)).toList();
+        if (pair.size() != 2 || pair.stream().anyMatch(q -> !Objects.equals(q.getQcLine(), QC_LINE_PROCESS)
+                && !Objects.equals(q.getQcLine(), QC_LINE_SALES)))
+            throw new IllegalArgumentException("琚嬬爜 " + code + " 鐨勫弻绾胯川妫�璁板綍涓嶅畬鏁达紝鏃犳硶鍏徃瀹℃壒");
+        if (pair.stream().anyMatch(q -> !Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL).contains(q.getWorkflowStatus())))
+            throw new IllegalArgumentException("琚嬬爜 " + code + " 鐨勫弻绾胯川妫�灏氭湭鍏ㄩ儴瀹屾垚锛屼笉鑳借繘琛屽叕鍙歌川妫�瀹℃壒");
+        boolean passed = pair.stream().allMatch(q -> Objects.equals(q.getWorkflowStatus(), WAIT_COMPANY));
+        MesQcSeedQualityDO representative = pair.stream()
+                .filter(q -> Objects.equals(q.getQcLine(), QC_LINE_PROCESS)).findFirst().orElse(pair.get(0));
+        LocalDateTime now = LocalDateTime.now();
+        if (approved) {
+            finalizeStock(representative, passed);
+            qualityMapper.updateBatch(pair.stream().map(quality -> MesQcSeedQualityDO.builder()
+                    .id(quality.getId()).workflowStatus(passed ? RELEASED : SCRAPPED)
+                    .companyApprovedAt(now).build()).toList(), WRITE_BATCH_SIZE);
+        } else {
+            qualityMapper.clearCompanyProcessInstance(code);
+            pair.forEach(quality -> qualityMapper.clearLineProcessInstance(quality.getId()));
+            qualityMapper.updateBatch(pair.stream().map(quality -> MesQcSeedQualityDO.builder()
+                    .id(quality.getId()).workflowStatus(WAIT_RETEST).companyApprovedAt(now).build()).toList(), WRITE_BATCH_SIZE);
+        }
+        actionMapper.insertBatch(pair.stream().map(quality -> newAction(quality.getId(),
+                approved ? "COMPANY_APPROVE" : "COMPANY_REJECT", quality.getResult(), opinion, null, operatorId, now)).toList(),
+                WRITE_BATCH_SIZE);
     }
 
     @Override @Transactional(rollbackFor = Exception.class)
@@ -563,6 +852,7 @@
         List<Long> confirmed = approvalIds(ids);
         List<MesQcSeedQualityDO> qualities = qualityMapper.selectListByIdsForUpdate(confirmed);
         validateLockedIds(confirmed, qualities);
+        qualities.forEach(MesQcSeedQualityServiceImpl::requireLegacyApproval);
         if (qualities.stream().anyMatch(quality -> !Set.of(WAIT_PROCESS, WAIT_INITIAL_FAIL_REVIEW).contains(quality.getWorkflowStatus())))
             throw new IllegalArgumentException("鎵�閫夎褰曞寘鍚笉鑳借繘琛屽垵妫�瀹℃牳鐨勭姸鎬侊紝璇峰埛鏂板悗閲嶈瘯");
         approverService.validateApprover(MesQcApprovalTypeEnum.SEED_QUALITY.getType(),
@@ -570,10 +860,17 @@
         if (!approved && StrUtil.isBlank(opinion)) throw new IllegalArgumentException("椹冲洖鏃惰濉啓瀹℃壒鎰忚");
         LocalDateTime now = LocalDateTime.now();
         Long operatorId = getLoginUserId();
+        List<MesQcSeedQualityDO> production = qualities.stream()
+                .filter(quality -> !Objects.equals(quality.getBizType(), BIZ_TYPE_RETURN)).toList();
+        qualities.stream().filter(quality -> Objects.equals(quality.getBizType(), BIZ_TYPE_RETURN))
+                .forEach(quality -> applyProcessApproval(quality, approved, opinion, false));
+        if (production.isEmpty()) return;
         int next = approved ? WAIT_RETEST : WAIT_INITIAL_RECHECK;
-        qualityMapper.updateBatch(qualities.stream().map(quality -> MesQcSeedQualityDO.builder().id(quality.getId())
-                .workflowStatus(next).processApprovedAt(now).build()).toList(), WRITE_BATCH_SIZE);
-        actionMapper.insertBatch(qualities.stream().map(quality -> newAction(quality.getId(),
+        qualityMapper.updateBatch(production.stream().map(quality -> MesQcSeedQualityDO.builder().id(quality.getId())
+                .workflowStatus(next)
+                .processApprovedAt(now).build()).toList(), WRITE_BATCH_SIZE);
+        if (!approved) production.forEach(quality -> qualityMapper.clearLineProcessInstance(quality.getId()));
+        actionMapper.insertBatch(production.stream().map(quality -> newAction(quality.getId(),
                 approved ? "PROCESS_APPROVE" : "PROCESS_REJECT", quality.getResult(), opinion, null, operatorId, now)).toList(),
                 WRITE_BATCH_SIZE);
     }
@@ -583,23 +880,340 @@
         List<Long> confirmed = approvalIds(ids);
         List<MesQcSeedQualityDO> qualities = qualityMapper.selectListByIdsForUpdate(confirmed);
         validateLockedIds(confirmed, qualities);
-        if (qualities.stream().anyMatch(quality -> !Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL).contains(quality.getWorkflowStatus())))
-            throw new IllegalArgumentException("鎵�閫夎褰曞寘鍚笉鑳借繘琛屽妫�瀹℃牳鐨勭姸鎬侊紝璇峰埛鏂板悗閲嶈瘯");
+        qualities.forEach(MesQcSeedQualityServiceImpl::requireLegacyApproval);
+        if (qualities.stream().anyMatch(quality -> Objects.equals(quality.getBizType(), BIZ_TYPE_RETURN)))
+            throw new IllegalArgumentException("缁忛攢鍟嗛��璐ц川妫�璇蜂娇鐢ㄩ攢鍞儴瀹℃壒");
         approverService.validateApprover(MesQcApprovalTypeEnum.SEED_QUALITY.getType(),
                 MesQcApproveNodeEnum.COMPANY.getNode(), getLoginUserId());
         if (!approved && StrUtil.isBlank(opinion)) throw new IllegalArgumentException("椹冲洖鏃惰濉啓瀹℃壒鎰忚");
-        if (approved) {
-            for (MesQcSeedQualityDO quality : qualities) finalizeStock(quality, quality.getWorkflowStatus() == WAIT_COMPANY);
-        }
+        Long operatorId = getLoginUserId();
+        // 鍏徃瀹℃壒浠ヨ涓哄崟浣嶆垚瀵瑰鐞嗭紝鍚屼竴琚嬬殑涓ゆ潯绾胯褰曚竴娆″鎵�
+        qualities.stream().map(MesQcSeedQualityDO::getCode).distinct().sorted()
+                .forEach(code -> applyCompanyApproval(code, approved, opinion, operatorId));
+    }
+
+    // ==================== 缁忛攢鍟嗛��璐ц川妫� ====================
+
+    @Override
+    public Map<String, Object> resolveReturnQuality(Long returnSalesId) {
+        MesWmReturnSalesDO returnSales = returnSalesService.getReturnSales(returnSalesId);
+        if (returnSales == null) throw new IllegalArgumentException("閿�鍞��璐у崟涓嶅瓨鍦�");
+        List<MesWmReturnSalesLineDO> lines = returnSalesLineService.getReturnSalesLineListByReturnId(returnSalesId);
+        MesWmReturnSalesLineDO first = lines.stream().filter(line -> line.getItemId() != null).findFirst().orElse(null);
+        Long itemId = first == null ? null : first.getItemId();
+        MesQcSeedQualityDO quality = qualityMapper.selectByReturnSalesId(returnSalesId);
+        Map<String, Object> result = new HashMap<>();
+        result.put("returnSalesId", returnSales.getId());
+        result.put("returnSalesCode", returnSales.getCode());
+        result.put("returnSalesStatus", returnSales.getStatus());
+        result.put("itemId", itemId);
+        result.put("batchId", first == null ? null : first.getBatchId());
+        result.put("batchCode", first == null ? null : first.getBatchCode());
+        result.put("indicators", toRangePayload(itemId));
+        result.put("qualityId", quality == null ? null : quality.getId());
+        result.put("result", quality == null ? null : quality.getResult());
+        result.put("workflowStatus", quality == null ? null : quality.getWorkflowStatus());
+        return result;
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long submitReturnQuality(MesQcSeedReturnQualitySubmitReqVO reqVO) {
+        MesWmReturnSalesDO returnSales = returnSalesService.getReturnSales(reqVO.getReturnSalesId());
+        if (returnSales == null) throw new IllegalArgumentException("閿�鍞��璐у崟涓嶅瓨鍦�");
+        if (qualityMapper.selectByReturnSalesId(returnSales.getId()) != null)
+            throw new IllegalArgumentException("璇ラ攢鍞��璐у崟宸插畬鎴愰��璐ц川妫�锛岀姝㈤噸澶嶆彁浜�");
+        if (!Set.of(MesWmReturnSalesStatusEnum.PREPARE.getStatus(),
+                MesWmReturnSalesStatusEnum.CONFIRMED.getStatus()).contains(returnSales.getStatus()))
+            throw new IllegalArgumentException("褰撳墠閿�鍞��璐у崟鐘舵�佷笉鑳借繘琛岄��璐ц川妫�");
+        List<MesWmReturnSalesLineDO> lines = returnSalesLineService.getReturnSalesLineListByReturnId(returnSales.getId());
+        if (CollUtil.isEmpty(lines)) throw new IllegalArgumentException("閿�鍞��璐у崟娌℃湁鏄庣粏锛屾棤娉曡川妫�");
+        MesWmReturnSalesLineDO first = lines.stream().filter(line -> line.getItemId() != null).findFirst()
+                .orElseThrow(() -> new IllegalArgumentException("閿�鍞��璐у崟鏄庣粏缂哄皯鐗╂枡锛屾棤娉曡川妫�"));
+        Map<String, IndicatorMeta> expected = requireEffectiveIndicators(first.getItemId());
+        boolean passed = validateIndicators(reqVO.getIndicators(), expected);
+        if (!passed && StrUtil.isBlank(reqVO.getReason())) throw new IllegalArgumentException("璐ㄦ涓嶅悎鏍煎繀椤诲~鍐欏師鍥�");
         LocalDateTime now = LocalDateTime.now();
         Long operatorId = getLoginUserId();
-        List<MesQcSeedQualityDO> updates = qualities.stream().map(quality -> MesQcSeedQualityDO.builder().id(quality.getId())
-                .workflowStatus(approved ? (quality.getWorkflowStatus() == WAIT_COMPANY ? RELEASED : SCRAPPED) : WAIT_RETEST)
-                .companyApprovedAt(now).build()).toList();
-        qualityMapper.updateBatch(updates, WRITE_BATCH_SIZE);
-        actionMapper.insertBatch(qualities.stream().map(quality -> newAction(quality.getId(),
-                approved ? "COMPANY_APPROVE" : "COMPANY_REJECT", quality.getResult(), opinion, null, operatorId, now)).toList(),
+        int result = passed ? 1 : 2;
+        MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder()
+                .code(returnSales.getCode()).qcLine(QC_LINE_SALES).bizType(BIZ_TYPE_RETURN)
+                .itemId(first.getItemId()).batchId(first.getBatchId()).batchCode(first.getBatchCode())
+                .result(result).reason(passed ? "" : reqVO.getReason().trim())
+                // 鍒濇鍚堟牸杩涘叆閿�鍞儴瀹℃壒锛涗笉鍚堟牸鍏堝妫�銆�
+                .workflowStatus(passed ? WAIT_PROCESS : WAIT_RETEST)
+                .returnSalesId(returnSales.getId()).returnSalesCode(returnSales.getCode())
+                .inspectorId(operatorId).inspectedAt(now).build();
+        qualityMapper.insert(quality);
+        for (MesQcSeedQualitySubmitReqVO.Indicator item : reqVO.getIndicators()) {
+            IndicatorMeta meta = expected.get(item.getCode());
+            indicatorMapper.insert(MesQcSeedQualityIndicatorDO.builder().qualityId(quality.getId())
+                    .indicatorCode(item.getCode()).indicatorName(meta.name()).unit(meta.unit())
+                    .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build());
+        }
+        recordAction(quality.getId(), "INITIAL", result, reqVO.getReason(), reqVO.getIndicators());
+        returnSalesService.updateReturnSalesStatus(returnSales.getId(), MesWmReturnSalesStatusEnum.CONFIRMED.getStatus());
+        if (passed) afterCommit(() -> startReturnProcess(quality, operatorId));
+        return quality.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void returnApprove(Long id, boolean approved, String opinion) {
+        MesQcSeedQualityDO quality = qualityMapper.selectByIdForUpdate(id);
+        if (quality == null || !Objects.equals(quality.getBizType(), BIZ_TYPE_RETURN))
+            throw new IllegalArgumentException("璐ㄦ璁板綍涓嶅瓨鍦ㄦ垨涓嶆槸缁忛攢鍟嗛��璐ц川妫�");
+        requireLegacyApproval(quality);
+        if (!Objects.equals(quality.getWorkflowStatus(), WAIT_PROCESS))
+            throw new IllegalArgumentException("褰撳墠鐘舵�佷笉鑳借繘琛岄攢鍞儴瀹℃壒");
+        approverService.validateApprover(MesQcApprovalTypeEnum.SEED_QUALITY.getType(),
+                MesQcApproveNodeEnum.PROCESS.getNode(), getLoginUserId());
+        applyReturnApproval(quality, approved, opinion, getLoginUserId(), LocalDateTime.now());
+    }
+
+    /**
+     * 閿�鍞儴棰嗗瀹℃壒锛氶�氳繃鍒欓��璐у崟杩涘叆鎵ц锛涗笉閫氳繃鍒欎笉浜堥��璐у苟鍙栨秷閫�璐у崟锛岃川妫�璁板綍鐣欏瓨銆�
+     */
+    private void applyReturnApproval(MesQcSeedQualityDO quality, boolean approved, String opinion,
+                                     Long operatorId, LocalDateTime now) {
+        if (!approved && StrUtil.isBlank(opinion)) throw new IllegalArgumentException("椹冲洖鏃惰濉啓瀹℃壒鎰忚");
+        if (approved && Objects.equals(quality.getResult(), 1)) {
+            returnSalesService.updateReturnSalesStatus(quality.getReturnSalesId(),
+                    MesWmReturnSalesStatusEnum.APPROVING.getStatus());
+            qualityMapper.updateById(MesQcSeedQualityDO.builder().id(quality.getId())
+                    .workflowStatus(RELEASED).processApprovedAt(now).build());
+        } else {
+            cancelReturnSalesQuietly(quality.getReturnSalesId());
+            qualityMapper.updateById(MesQcSeedQualityDO.builder().id(quality.getId())
+                    .workflowStatus(SCRAPPED).processApprovedAt(now).build());
+        }
+        actionMapper.insert(newAction(quality.getId(), approved ? "RETURN_APPROVE" : "RETURN_REJECT",
+                quality.getResult(), opinion, null, operatorId, now));
+    }
+
+    // ==================== BPM 娴佺▼鍥炶皟 ====================
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void handleLineProcessResult(String processInstanceId, Integer qcLine, String batchCode, boolean approved, String reason) {
+        if (StrUtil.isBlank(processInstanceId) || qcLine == null || StrUtil.isBlank(batchCode)) return;
+        List<MesQcSeedQualityDO> pending = qualityMapper.selectListByProcessInstanceId(processInstanceId).stream()
+                .filter(quality -> Objects.equals(quality.getQcLine(), qcLine)
+                        && Objects.equals(quality.getBatchCode(), batchCode)
+                        && !isReturnQuality(quality)
+                        && Set.of(WAIT_PROCESS, WAIT_INITIAL_FAIL_REVIEW).contains(quality.getWorkflowStatus()))
+                .toList();
+        if (pending.isEmpty()) {
+            log.info("[handleLineProcessResult][鎵规 {} 璐ㄦ绾� {} 鏃犲緟瀹℃壒璁板綍锛屽拷鐣� BPM 浜嬩欢]", batchCode, qcLine);
+            return;
+        }
+        List<Long> ids = pending.stream().map(MesQcSeedQualityDO::getId).sorted().toList();
+        List<MesQcSeedQualityDO> locked = qualityMapper.selectListByIdsForUpdate(ids);
+        validateLockedIds(ids, locked);
+        LocalDateTime now = LocalDateTime.now();
+        int next = approved ? WAIT_COMPANY : WAIT_INITIAL_RECHECK;
+        String finalReason = StrUtil.blankToDefault(reason, approved ? "瀹℃壒閫氳繃" : "瀹℃壒椹冲洖");
+        qualityMapper.updateBatch(locked.stream().map(quality -> MesQcSeedQualityDO.builder().id(quality.getId())
+                .workflowStatus(approved && Objects.equals(quality.getResult(), 2) ? WAIT_COMPANY_FAIL : next)
+                .processApprovedAt(now).build()).toList(), WRITE_BATCH_SIZE);
+        if (!approved) locked.forEach(quality -> qualityMapper.clearLineProcessInstance(quality.getId()));
+        actionMapper.insertBatch(locked.stream().map(quality -> newAction(quality.getId(),
+                approved ? "PROCESS_APPROVE" : "PROCESS_REJECT", quality.getResult(), finalReason, null, null, now)).toList(),
                 WRITE_BATCH_SIZE);
+        if (approved) {
+            Long inspectorId = locked.stream().map(MesQcSeedQualityDO::getInspectorId)
+                    .filter(Objects::nonNull).findFirst().orElse(null);
+            afterCommit(() -> maybeStartCompanyProcess(batchCode, inspectorId));
+        }
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void handleCompanyProcessResult(String processInstanceId, String businessKey, boolean approved, String reason) {
+        if (StrUtil.isBlank(processInstanceId)) return;
+        // 鍏徃瀹℃壒鎸夌敓浜ф壒娆″彂璧凤紝涓�涓祦绋嬪疄渚嬭鐩栬鎵规鐨勬墍鏈夎锛�
+        // 鎸夋祦绋嬪疄渚嬬紪鍙峰洖鏌ュ彲鍚屾椂鍏煎鍘嗗彶鎸夎鐮佸彂璧风殑瀹℃壒銆�
+        List<MesQcSeedQualityDO> pending = qualityMapper.selectListByCompanyProcessInstanceId(processInstanceId).stream()
+                .filter(quality -> !isReturnQuality(quality)
+                        && Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL).contains(quality.getWorkflowStatus()))
+                .toList();
+        if (pending.isEmpty()) {
+            log.info("[handleCompanyProcessResult][娴佺▼ {} 鏃犲緟鍏徃瀹℃壒璁板綍锛屽拷鐣� BPM 浜嬩欢]", processInstanceId);
+            return;
+        }
+        String finalReason = StrUtil.blankToDefault(reason, approved ? "鍏徃璐ㄦ瀹℃壒閫氳繃" : "鍏徃璐ㄦ瀹℃壒椹冲洖");
+        // 鏀捐/鎶ュ簾浠ヨ涓哄崟浣嶈惤搴擄紝鍚屼竴鎵规鐨勫涓渚濇澶勭悊
+        pending.stream().map(MesQcSeedQualityDO::getCode).distinct().sorted()
+                .forEach(code -> applyCompanyApproval(code, approved, finalReason, null));
+        log.info("[handleCompanyProcessResult][鍏徃璐ㄦ瀹℃壒 {} 瀹屾垚锛屾壒娆�/琚嬬爜 {} 鍏卞鐞� {} 鏉¤褰昡",
+                processInstanceId, businessKey, pending.size());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void handleReturnProcessResult(String processInstanceId, Long returnSalesId, boolean approved, String reason) {
+        if (StrUtil.isBlank(processInstanceId) || returnSalesId == null) return;
+        MesQcSeedQualityDO quality = qualityMapper.selectByReturnSalesIdForUpdate(returnSalesId);
+        if (quality == null || !Objects.equals(quality.getBizType(), BIZ_TYPE_RETURN)
+                || !Objects.equals(quality.getProcessInstanceId(), processInstanceId)
+                || !Objects.equals(quality.getWorkflowStatus(), WAIT_PROCESS)) {
+            log.info("[handleReturnProcessResult][閿�鍞��璐у崟 {} 鏃犲緟瀹℃壒閫�璐ц川妫�锛屽拷鐣� BPM 浜嬩欢]", returnSalesId);
+            return;
+        }
+        applyReturnApproval(quality, approved, StrUtil.blankToDefault(reason, approved ? "瀹℃壒閫氳繃" : "瀹℃壒椹冲洖"),
+                null, LocalDateTime.now());
+    }
+
+    private void startLineProcess(Integer qcLine, String batchCode, Long operatorId) {
+        if (qcLine == null || StrUtil.isBlank(batchCode) || operatorId == null) return;
+        List<MesQcSeedQualityDO> pending = qualityMapper.selectListByBatchCode(batchCode).stream()
+                .filter(quality -> Objects.equals(quality.getQcLine(), qcLine)
+                        && !isReturnQuality(quality)
+                        && StrUtil.isBlank(quality.getProcessInstanceId())
+                        && Set.of(WAIT_PROCESS, WAIT_INITIAL_FAIL_REVIEW).contains(quality.getWorkflowStatus()))
+                .toList();
+        if (pending.isEmpty()) return;
+        Map<String, Object> variables = new HashMap<>();
+        variables.put("qcLine", qcLine);
+        variables.put("batchCode", batchCode);
+        String processInstanceId = processInstanceApi.createProcessInstance(operatorId,
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(LINE_PROCESS_KEY)
+                        .setBusinessKey(qcLine + "#" + batchCode)
+                        .setVariables(variables));
+        for (MesQcSeedQualityDO quality : pending) {
+            qualityMapper.updateById(MesQcSeedQualityDO.builder().id(quality.getId())
+                    .processInstanceId(processInstanceId).build());
+        }
+        log.info("[startLineProcess][鎵规 {} 璐ㄦ绾� {} 宸插彂璧锋湰绾块瀵煎鎵� {}", batchCode, qcLine, processInstanceId);
+    }
+
+    /**
+     * 鍙岀嚎閮ㄩ棬瀹℃壒鍏ㄩ儴瀹屾垚鍚庯紝鎸夌敓浜ф壒娆$粺涓�鍙戣捣涓�娆″叕鍙歌川妫�瀹℃壒銆�
+     * 鍚屼竴鎵规鐨勫琚嬪叡鐢ㄥ悓涓�涓祦绋嬪疄渚嬶紝閬垮厤姣忚鍚勫彂涓�鏉″鎵广��
+     */
+    private void maybeStartCompanyProcess(String batchCode, Long operatorId) {
+        if (StrUtil.isBlank(batchCode) || operatorId == null) return;
+        List<MesQcSeedQualityDO> records = qualityMapper.selectListByBatchCode(batchCode).stream()
+                .filter(quality -> !isReturnQuality(quality))
+                // 宸叉斁琛�/宸叉姤搴熺殑璁板綍灞炰簬宸茬粓缁撶殑鎵规锛屼笉鍐嶅弬涓庢柊鐨勫叕鍙稿鎵�
+                .filter(quality -> !Set.of(RELEASED, SCRAPPED).contains(quality.getWorkflowStatus()))
+                .toList();
+        if (records.isEmpty()) return;
+        if (records.stream().anyMatch(quality -> !Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL)
+                .contains(quality.getWorkflowStatus()))) return;
+        if (records.stream().anyMatch(quality -> StrUtil.isNotBlank(quality.getCompanyProcessInstanceId()))) return;
+        Map<String, Object> variables = new HashMap<>();
+        variables.put("batchCode", batchCode);
+        // 鍏煎娴佺▼鏉′欢涓紩鐢ㄧ殑 code 鍙橀噺
+        variables.put("code", batchCode);
+        variables.put("passed", records.stream().allMatch(quality -> Objects.equals(quality.getWorkflowStatus(), WAIT_COMPANY)));
+        String processInstanceId = processInstanceApi.createProcessInstance(operatorId,
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(COMPANY_PROCESS_KEY)
+                        .setBusinessKey(batchCode)
+                        .setVariables(variables));
+        for (MesQcSeedQualityDO quality : records) {
+            qualityMapper.updateById(MesQcSeedQualityDO.builder().id(quality.getId())
+                    .companyProcessInstanceId(processInstanceId).build());
+        }
+        log.info("[maybeStartCompanyProcess][鎵规 {} 鍙岀嚎璐ㄦ瀹屾垚锛屽凡鍙戣捣鍏徃璐ㄦ瀹℃壒 {}锛岃鐩� {} 鏉¤褰昡",
+                batchCode, processInstanceId, records.size());
+    }
+
+    private void startReturnProcess(MesQcSeedQualityDO quality, Long operatorId) {
+        if (quality == null || quality.getReturnSalesId() == null || StrUtil.isNotBlank(quality.getProcessInstanceId()))
+            return;
+        Map<String, Object> variables = new HashMap<>();
+        variables.put("returnSalesId", quality.getReturnSalesId());
+        variables.put("returnSalesCode", quality.getReturnSalesCode());
+        variables.put("code", quality.getCode());
+        String processInstanceId = processInstanceApi.createProcessInstance(operatorId,
+                new BpmProcessInstanceCreateReqDTO()
+                        .setProcessDefinitionKey(RETURN_PROCESS_KEY)
+                        .setBusinessKey(String.valueOf(quality.getReturnSalesId()))
+                        .setVariables(variables));
+        qualityMapper.updateById(MesQcSeedQualityDO.builder().id(quality.getId())
+                .processInstanceId(processInstanceId).build());
+        log.info("[startReturnProcess][閿�鍞��璐у崟 {} 宸插彂璧烽攢鍞儴棰嗗瀹℃壒 {}]", quality.getReturnSalesId(), processInstanceId);
+    }
+
+    @Override
+    public void retryBpmProcess(Long id) {
+        MesQcSeedQualityDO quality = qualityMapper.selectById(id);
+        if (quality == null) throw invalidParamException("璐ㄦ璁板綍涓嶅瓨鍦�");
+        Long operatorId = getLoginUserId();
+        if (Objects.equals(quality.getBizType(), BIZ_TYPE_RETURN)) {
+            if (!Objects.equals(quality.getWorkflowStatus(), WAIT_PROCESS))
+                throw invalidParamException("褰撳墠閫�璐ц川妫�涓嶉渶瑕佸彂璧峰鎵�");
+            if (StrUtil.isNotBlank(quality.getProcessInstanceId()))
+                throw invalidParamException("閫�璐ц川妫�瀹℃壒娴佺▼宸茬粡鍙戣捣");
+            startReturnProcess(quality, operatorId);
+        } else if (Set.of(WAIT_PROCESS, WAIT_INITIAL_FAIL_REVIEW).contains(quality.getWorkflowStatus())) {
+            if (StrUtil.isNotBlank(quality.getProcessInstanceId()))
+                throw invalidParamException("閮ㄩ棬瀹℃壒娴佺▼宸茬粡鍙戣捣");
+            startLineProcess(quality.getQcLine(), quality.getBatchCode(), operatorId);
+        } else if (Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL).contains(quality.getWorkflowStatus())) {
+            String batchCode = quality.getBatchCode();
+            if (StrUtil.isBlank(batchCode))
+                throw invalidParamException("璐ㄦ璁板綍鏈粦瀹氱敓浜ф壒娆★紝涓嶈兘鍙戣捣鍏徃瀹℃壒");
+            List<MesQcSeedQualityDO> batchRecords = qualityMapper.selectListByBatchCode(batchCode).stream()
+                    .filter(q -> !isReturnQuality(q))
+                    .filter(q -> !Set.of(RELEASED, SCRAPPED).contains(q.getWorkflowStatus()))
+                    .toList();
+            if (batchRecords.isEmpty() || batchRecords.stream().anyMatch(q -> !Set.of(WAIT_COMPANY, WAIT_COMPANY_FAIL)
+                    .contains(q.getWorkflowStatus())))
+                throw invalidParamException("鍚屾壒娆″弻绾块儴闂ㄥ鎵瑰皻鏈叏閮ㄥ畬鎴愶紝涓嶈兘鍙戣捣鍏徃瀹℃壒");
+            if (batchRecords.stream().anyMatch(q -> StrUtil.isNotBlank(q.getCompanyProcessInstanceId())))
+                throw invalidParamException("鍏徃璐ㄦ瀹℃壒娴佺▼宸茬粡鍙戣捣");
+            maybeStartCompanyProcess(batchCode, operatorId);
+        } else {
+            throw invalidParamException("褰撳墠璐ㄦ鐘舵�佷笉闇�瑕佸彂璧峰鎵�");
+        }
+    }
+
+    private void cancelReturnSalesQuietly(Long returnSalesId) {
+        if (returnSalesId == null) return;
+        try {
+            returnSalesService.cancelReturnSales(returnSalesId);
+        } catch (Exception ex) {
+            log.warn("[cancelReturnSalesQuietly][閿�鍞��璐у崟 {} 鍙栨秷澶辫触: {}]", returnSalesId, ex.getMessage());
+        }
+    }
+
+    private void afterCommit(Runnable action) {
+        TransactionSynchronization synchronization = new TransactionSynchronization() {
+            @Override
+            public void afterCommit() {
+                try {
+                    action.run();
+                } catch (Exception ex) {
+                    log.error("[afterCommit][BPM 娴佺▼鍚姩澶辫触锛岃淇娴佺▼閰嶇疆骞跺湪璐ㄦ璁板綍涓噸鏂板彂璧峰鎵筣", ex);
+                }
+            }
+        };
+        if (TransactionSynchronizationManager.isSynchronizationActive()) {
+            TransactionSynchronizationManager.registerSynchronization(synchronization);
+        } else {
+            synchronization.afterCommit();
+        }
+    }
+
+    /** 鍘嗗彶鏁版嵁 bizType 涓虹┖锛屾寜鐢熶骇鍏ュ簱璐ㄦ澶勭悊 */
+    private static void requireQcLine(Integer qcLine) {
+        if (!Objects.equals(qcLine, QC_LINE_PROCESS) && !Objects.equals(qcLine, QC_LINE_SALES))
+            throw new IllegalArgumentException("璐ㄦ绾垮繀椤讳负鍔犲伐閮ㄦ垨閿�鍞儴");
+    }
+
+    /** 鐢熶骇鍙岀嚎璁板綍鍙兘璧� BPM 瀹℃壒锛涚粡閿�鍟嗛��璐ц川妫�涓嶆媶鍒嗚川妫�绾匡紝浠嶅彲浣跨敤浜哄伐瀹℃壒鎺ュ彛銆� */
+    private static void requireLegacyApproval(MesQcSeedQualityDO quality) {
+        if (!isReturnQuality(quality) && (Objects.equals(quality.getQcLine(), QC_LINE_PROCESS)
+                || Objects.equals(quality.getQcLine(), QC_LINE_SALES)))
+            throw new IllegalArgumentException("璇ヨ川妫�璁板綍鐢� BPM 瀹℃壒锛岃鍦ㄦ祦绋嬪緟鍔炰腑澶勭悊");
+    }
+
+    private static boolean isReturnQuality(MesQcSeedQualityDO quality) {
+        return Objects.equals(quality.getBizType(), BIZ_TYPE_RETURN);
     }
 
     private List<Long> approvalIds(List<Long> ids) {
@@ -626,21 +1240,95 @@
         return quality;
     }
 
-    private boolean validateIndicators(List<MesQcSeedQualitySubmitReqVO.Indicator> indicators) {
-        if (indicators == null || indicators.size() != REQUIRED.size()) throw new IllegalArgumentException("蹇呴』鎻愪氦鍥涢」鎸囨爣");
+    private record IndicatorMeta(String code, String name, String unit) { }
+
+    private Map<String, IndicatorMeta> requireEffectiveIndicators(Long itemId) {
+        Map<String, IndicatorMeta> result = new LinkedHashMap<>();
+        for (MesQcSeedIndicatorRangeDO range : rangeService.getEffectiveRanges(itemId)) {
+            result.put(range.getIndicatorCode(), new IndicatorMeta(range.getIndicatorCode(),
+                    StrUtil.blankToDefault(range.getIndicatorName(), range.getIndicatorCode()),
+                    StrUtil.blankToDefault(range.getUnit(), "%")));
+        }
+        if (result.isEmpty())
+            throw new IllegalArgumentException("璇ヤ骇鍝佸皻鏈厤缃川妫�椤癸紝璇峰厛鍦ㄣ�岃川妫�椤瑰悎鏍艰寖鍥撮厤缃�嶄腑缁存姢");
+        return result;
+    }
+
+    private Map<String, IndicatorMeta> toMetaMap(Collection<MesQcSeedQualityIndicatorDO> indicators, Long itemId) {
+        Map<String, MesQcSeedIndicatorRangeDO> config = rangeService.getEffectiveMap(itemId);
+        Map<String, IndicatorMeta> result = new LinkedHashMap<>();
+        for (MesQcSeedQualityIndicatorDO indicator : indicators) {
+            MesQcSeedIndicatorRangeDO range = config.get(indicator.getIndicatorCode());
+            String name = StrUtil.blankToDefault(indicator.getIndicatorName(),
+                    range == null ? indicator.getIndicatorCode() : range.getIndicatorName());
+            String unit = StrUtil.blankToDefault(indicator.getUnit(), range == null ? "%" : range.getUnit());
+            result.put(indicator.getIndicatorCode(), new IndicatorMeta(indicator.getIndicatorCode(), name, unit));
+        }
+        return result;
+    }
+
+    private boolean validateIndicators(List<MesQcSeedQualitySubmitReqVO.Indicator> indicators,
+                                       Map<String, IndicatorMeta> expected) {
+        if (indicators == null || indicators.isEmpty()) throw new IllegalArgumentException("璇疯嚦灏戞彁浜や竴椤硅川妫�鎸囨爣");
+        if (expected.isEmpty()) throw new IllegalArgumentException("璇ヤ骇鍝佸皻鏈厤缃川妫�椤�");
+        if (indicators.size() != expected.size())
+            throw new IllegalArgumentException("璐ㄦ椤规暟閲忎笌浜у搧閰嶇疆涓嶄竴鑷达紝璇峰埛鏂板悗閲嶆柊褰曞叆");
         Set<String> codes = new HashSet<>();
         boolean passed = true;
         for (MesQcSeedQualitySubmitReqVO.Indicator item : indicators) {
-            if (item == null || !REQUIRED.contains(item.getCode()) || !codes.add(item.getCode())
+            if (item == null || StrUtil.isBlank(item.getCode()) || !codes.add(item.getCode())
                     || item.getValue() == null || item.getMinValue() == null || item.getMaxValue() == null)
-                throw new IllegalArgumentException("鎸囨爣蹇呴』涓虹函搴︺�佸噣搴︺�佸彂鑺界巼銆佹按鍒嗕笖涓嶅彲閲嶅");
-            if (item.getMinValue().compareTo(item.getMaxValue()) > 0) throw new IllegalArgumentException("鎸囨爣涓嬮檺涓嶈兘澶т簬涓婇檺");
-            if (item.getValue().compareTo(BigDecimal.ZERO) < 0 || item.getValue().compareTo(new BigDecimal("100")) > 0
-                    || item.getMinValue().compareTo(BigDecimal.ZERO) < 0 || item.getMaxValue().compareTo(new BigDecimal("100")) > 0)
-                throw new IllegalArgumentException("鐧惧垎姣旀寚鏍囧繀椤诲浜� 0鈥�100");
+                throw new IllegalArgumentException("璐ㄦ椤圭殑瀹炴祴鍊煎拰鍚堟牸鑼冨洿蹇呴』瀹屾暣涓斾笉鍙噸澶�");
+            if (!expected.containsKey(item.getCode()))
+                throw new IllegalArgumentException("璐ㄦ椤规湭鍦ㄤ骇鍝侀厤缃腑鍚敤锛�" + item.getCode());
+            if (item.getMinValue().compareTo(item.getMaxValue()) > 0) throw new IllegalArgumentException("璐ㄦ椤逛笅闄愪笉鑳藉ぇ浜庝笂闄�");
+            if (item.getValue().compareTo(BigDecimal.ZERO) < 0 || item.getMinValue().compareTo(BigDecimal.ZERO) < 0
+                    || item.getMaxValue().compareTo(BigDecimal.ZERO) < 0)
+                throw new IllegalArgumentException("璐ㄦ椤规暟鍊间笉鑳戒负璐熸暟");
             if (item.getValue().compareTo(item.getMinValue()) < 0 || item.getValue().compareTo(item.getMaxValue()) > 0) passed = false;
         }
+        if (!codes.equals(expected.keySet())) throw new IllegalArgumentException("浜у搧璐ㄦ椤逛笉瀹屾暣锛岃鍒锋柊鍚庨噸鏂板綍鍏�");
         return passed;
+    }
+
+    private Long resolveItemId(Scope scope) {
+        Long itemId = scope.bags().stream().map(MesProBagCodeDO::getItemId).filter(Objects::nonNull).findFirst().orElse(null);
+        if (itemId != null || scope.bags().isEmpty()) return itemId;
+        Long batchId = scope.bags().get(0).getBatchId();
+        if (batchId == null) return null;
+        MesProBatchDO batch = proBatchService.getBatch(batchId);
+        return batch == null ? null : batch.getItemId();
+    }
+
+    private List<Map<String, Object>> toRangePayload(Long itemId) {
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (MesQcSeedIndicatorRangeDO range : rangeService.getEffectiveRanges(itemId)) {
+            Map<String, Object> item = new HashMap<>();
+            item.put("code", range.getIndicatorCode());
+            item.put("name", StrUtil.blankToDefault(range.getIndicatorName(), range.getIndicatorCode()));
+            item.put("unit", StrUtil.blankToDefault(range.getUnit(), "%"));
+            item.put("minValue", range.getMinValue());
+            item.put("maxValue", range.getMaxValue());
+            result.add(item);
+        }
+        return result;
+    }
+
+    private List<Map<String, Object>> toIndicatorPayload(List<MesQcSeedQualityIndicatorDO> indicators, Long itemId) {
+        Map<String, MesQcSeedIndicatorRangeDO> config = rangeService.getEffectiveMap(itemId);
+        List<Map<String, Object>> result = new ArrayList<>();
+        for (MesQcSeedQualityIndicatorDO indicator : indicators) {
+            MesQcSeedIndicatorRangeDO range = config.get(indicator.getIndicatorCode());
+            Map<String, Object> item = new HashMap<>();
+            item.put("code", indicator.getIndicatorCode());
+            item.put("name", StrUtil.blankToDefault(indicator.getIndicatorName(),
+                    range == null ? indicator.getIndicatorCode() : range.getIndicatorName()));
+            item.put("unit", StrUtil.blankToDefault(indicator.getUnit(), range == null ? "%" : range.getUnit()));
+            item.put("minValue", indicator.getMinValue());
+            item.put("maxValue", indicator.getMaxValue());
+            result.add(item);
+        }
+        return result;
     }
 
     private void recordAction(Long id, String action, Integer result, String opinion,
@@ -711,20 +1399,20 @@
     /**
      * 瑙f瀽鍚堟牸搴撶殑榛樿鍏ュ簱搴撳尯涓庡簱浣嶏細鍙栫涓�涓悓鏃堕厤缃簡搴撲綅鐨勯潪鍐荤粨搴撳尯銆�
      *
-     * 璐ㄦ鎸変粨搴撶淮搴﹂�夋嫨鍏ュ簱鐩爣锛屽簱鍖�/搴撲綅鐢辩郴缁熻嚜鍔ㄨ惤鐐癸紱鍚堟牸搴撶己灏戝彲鐢ㄥ簱鍖烘垨搴撲綅鏃讹紝
-     * 搴撳瓨浜嬪姟鏃犳硶鍐欏叆锛岀洿鎺ユ彁绀虹鐞嗗憳瀹屽杽浠撳簱閰嶇疆銆�
+     * 搴撳尯/搴撲綅涓哄彲閫夐」锛屼粨搴撴湭閰嶇疆鏃舵寜浠撳簱灞傜骇鐩存帴鍏ュ簱锛岃繑鍥� null銆�
      */
     private Long[] resolveQualifiedLocation(MesWmWarehouseDO qualifiedWarehouse) {
         List<MesWmWarehouseLocationDO> locations = warehouseLocationService.getWarehouseLocationList(qualifiedWarehouse.getId())
                 .stream().filter(location -> !Boolean.TRUE.equals(location.getFrozen())).toList();
-        if (CollUtil.isEmpty(locations))
-            throw new IllegalArgumentException("鍚堟牸搴撱��" + qualifiedWarehouse.getName() + "銆嶆湭閰嶇疆鍙敤搴撳尯锛岃鍏堝湪浠撳簱绠$悊涓畬鍠勫簱鍖轰笌搴撲綅");
+        if (CollUtil.isEmpty(locations)) {
+            return new Long[]{null, null};
+        }
         for (MesWmWarehouseLocationDO location : locations) {
             List<MesWmWarehouseAreaDO> areas = warehouseAreaService.getWarehouseAreaList(location.getId())
                     .stream().filter(area -> !Boolean.TRUE.equals(area.getFrozen())).toList();
             if (CollUtil.isNotEmpty(areas)) return new Long[]{location.getId(), areas.get(0).getId()};
         }
-        throw new IllegalArgumentException("鍚堟牸搴撱��" + qualifiedWarehouse.getName() + "銆嶆湭閰嶇疆鍙敤搴撲綅锛岃鍏堝湪浠撳簱绠$悊涓畬鍠勫簱鍖轰笌搴撲綅");
+        return new Long[]{null, null};
     }
 
     private MesWmMaterialStockDO getOrCreateQualifiedStock(MesWmMaterialStockDO tempStock, Long qualifiedWarehouseId,

--
Gitblit v1.9.3