From 5140e16bd1cb2bdf974d47a4342f9e27ef804655 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期六, 18 七月 2026 15:54:07 +0800
Subject: [PATCH] feat(erp): 增加销售订单MPS集成与发货通知单控制功能

---
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java       |   43 ++++++
 /dev/null                                                                                                          |  160 ----------------------
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java               |   92 +++++++++++++
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApi.java                           |   10 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java                 |    8 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApiImpl.java                           |   35 +++++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java  |    6 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierServiceImpl.java            |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java |   12 +
 9 files changed, 202 insertions(+), 168 deletions(-)

diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
index ad74f35..1280ab6 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
@@ -18,6 +18,7 @@
 import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOrderService;
 import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
 import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
 import cn.iocoder.yudao.module.mes.api.salesnotice.MesWmSalesNoticeApi;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
@@ -58,6 +59,8 @@
     private AdminUserApi adminUserApi;
     @Resource
     private MesWmSalesNoticeApi salesNoticeApi;
+    @Resource
+    private MesProMpsApi mpsApi;
 
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓閿�鍞鍗�")
@@ -171,6 +174,40 @@
         // 1.5 鍙戣揣閫氱煡鍗曚俊鎭�
         Map<Long, Boolean> salesNoticeMap = salesNoticeApi.checkSalesNoticeBySaleOrderIds(
                 convertSet(pageResult.getList(), ErpSaleOrderDO::getId));
+        // 1.6 妫�鏌ユ槸鍚﹀彲浠ョ敓鎴愬彂璐ч�氱煡鍗曪紙闇�瑕佺敓浜х殑鏄庣粏瀵瑰簲鐨� MPS 鏄惁宸插畬鎴愶級
+        // 绛涢�夊嚭闇�瑕佺敓浜х殑鏄庣粏ID
+        List<Long> needProductionItemIds = saleOrderItemList.stream()
+                .filter(item -> item.getNeedProduction() != null && item.getNeedProduction() == 1)
+                .map(ErpSaleOrderItemDO::getId)
+                .toList();
+        Map<Long, Boolean> canCreateSalesNoticeMap = new java.util.HashMap<>();
+        if (!needProductionItemIds.isEmpty()) {
+            // 鎸夎鍗曞垎缁勬槑缁咺D
+            Map<Long, List<Long>> orderItemIdsMap = new java.util.HashMap<>();
+            for (ErpSaleOrderItemDO item : saleOrderItemList) {
+                orderItemIdsMap.computeIfAbsent(item.getOrderId(), k -> new java.util.ArrayList<>())
+                        .add(item.getId());
+            }
+            // 妫�鏌ユ瘡涓鍗�
+            for (Map.Entry<Long, List<Long>> entry : orderItemIdsMap.entrySet()) {
+                List<Long> itemIds = entry.getValue().stream()
+                        .filter(id -> {
+                            ErpSaleOrderItemDO item = saleOrderItemList.stream()
+                                    .filter(i -> i.getId().equals(id))
+                                    .findFirst().orElse(null);
+                            return item != null && item.getNeedProduction() != null && item.getNeedProduction() == 1;
+                        }
+                ).toList();
+                if (itemIds.isEmpty()) {
+                    canCreateSalesNoticeMap.put(entry.getKey(), true);
+                } else {
+                    canCreateSalesNoticeMap.put(entry.getKey(), mpsApi.checkCanCreateSalesNotice(itemIds));
+                }
+            }
+        } else {
+            // 娌℃湁闇�瑕佺敓浜х殑鏄庣粏锛岄兘鍙互鐢熸垚鍙戣揣閫氱煡鍗�
+            pageResult.getList().forEach(order -> canCreateSalesNoticeMap.put(order.getId(), true));
+        }
         // 2. 寮�濮嬫嫾鎺�
         return BeanUtils.toBean(pageResult, ErpSaleOrderRespVO.class, saleOrder -> {
             saleOrder.setItems(BeanUtils.toBean(saleOrderItemMap.get(saleOrder.getId()), ErpSaleOrderRespVO.Item.class,
@@ -181,6 +218,12 @@
             MapUtils.findAndThen(userMap, Long.parseLong(saleOrder.getCreator()), user -> saleOrder.setCreatorName(user.getNickname()));
             // 璁剧疆鏄惁宸茬敓鎴愬彂璐ч�氱煡鍗�
             saleOrder.setHasSalesNotice(salesNoticeMap.getOrDefault(saleOrder.getId(), false));
+            // 璁剧疆鏄惁鍙互鐢熸垚鍙戣揣閫氱煡鍗�
+            saleOrder.setCanCreateSalesNotice(canCreateSalesNoticeMap.getOrDefault(saleOrder.getId(), true));
+            // 璁剧疆鏄惁鏈夐渶瑕佺敓浜х殑鏄庣粏
+            boolean hasNeedProduction = saleOrder.getItems().stream()
+                    .anyMatch(item -> item.getNeedProduction() != null && item.getNeedProduction() == 1);
+            saleOrder.setNeedProduction(hasNeedProduction ? 1 : 0);
         });
     }
 
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
index 34085c4..63d2389 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
@@ -109,6 +109,12 @@
     @Schema(description = "鏄惁宸茬敓鎴愬彂璐ч�氱煡鍗�", example = "false")
     private Boolean hasSalesNotice;
 
+    @Schema(description = "鏄惁鍙互鐢熸垚鍙戣揣閫氱煡鍗曪紙鏈夐渶瑕佺敓浜т笖鏈畬鎴愮殑鏄庣粏鏃朵负 false锛�", example = "true")
+    private Boolean canCreateSalesNotice;
+
+    @Schema(description = "鏄惁鏈夐渶瑕佺敓浜х殑鏄庣粏锛�0=鍚� 1=鏄級", example = "1")
+    private Integer needProduction;
+
     @Data
     public static class Item {
 
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierServiceImpl.java
index 64db7cd..9e56f4a 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpSupplierServiceImpl.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.erp.service.purchase;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -78,6 +79,9 @@
 
     @Override
     public List<ErpSupplierDO> getSupplierList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return CollUtil.newArrayList();
+        }
         return supplierMapper.selectByIds(ids);
     }
 
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
index 5018ce3..a9ad090 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
@@ -17,13 +17,20 @@
 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
 import cn.iocoder.yudao.module.erp.api.sale.enums.ErpSaleOrderOutStatusEnum;
 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
+import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsCreateReqDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -40,6 +47,7 @@
  */
 @Service
 @Validated
+@Slf4j
 public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
 
     @Resource
@@ -59,6 +67,11 @@
 
     @Resource
     private AdminUserApi adminUserApi;
+
+    @Resource
+    private MdmItemApi mdmItemApi;
+    @Resource
+    private MesProMpsApi mpsApi;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -157,6 +170,85 @@
         if (updateCount == 0) {
             throw exception(approve ? SALE_ORDER_APPROVE_FAIL : SALE_ORDER_PROCESS_FAIL);
         }
+
+        // 3. 瀹℃牳閫氳繃鏃讹紝涓洪渶瑕佺敓浜х殑鏄庣粏鍒涘缓 MPS
+        if (approve) {
+            createMpsForSaleOrder(id, saleOrder);
+        }
+    }
+
+    /**
+     * 涓洪攢鍞鍗曚腑闇�瑕佺敓浜х殑鏄庣粏鍒涘缓 MPS
+     *
+     * @param orderId   閿�鍞鍗曠紪鍙�
+     * @param saleOrder 閿�鍞鍗�
+     */
+    private void createMpsForSaleOrder(Long orderId, ErpSaleOrderDO saleOrder) {
+        List<ErpSaleOrderItemDO> items = saleOrderItemMapper.selectListByOrderId(orderId);
+        if (CollUtil.isEmpty(items)) {
+            return;
+        }
+
+        int mpsCount = 0;
+        for (ErpSaleOrderItemDO item : items) {
+            if (item.getNeedProduction() == null || item.getNeedProduction() != 1) {
+                continue;
+            }
+            try {
+                Long mpsId = createMpsForSaleOrderItem(saleOrder, item);
+                if (mpsId != null) {
+                    mpsCount++;
+                    // 鏇存柊閿�鍞鍗曟槑缁嗙殑 MPS 鍏宠仈
+                    saleOrderItemMapper.updateById(new ErpSaleOrderItemDO()
+                            .setId(item.getId())
+                            .setMpsId(mpsId));
+                }
+            } catch (Exception e) {
+                log.error("[createMpsForSaleOrder] 鍒涘缓 MPS 澶辫触锛宻aleOrderItemId={}", item.getId(), e);
+            }
+        }
+        log.info("[createMpsForSaleOrder] 閿�鍞鍗�({}) 瀹℃牳瀹屾垚锛屽垱寤� MPS {} 鏉�", orderId, mpsCount);
+    }
+
+    /**
+     * 涓洪攢鍞鍗曟槑缁嗗垱寤� MPS
+     */
+    private Long createMpsForSaleOrderItem(ErpSaleOrderDO saleOrder, ErpSaleOrderItemDO item) {
+        // 鑾峰彇 MDM 鐗╂枡淇℃伅锛堢敤浜庤幏鍙栦骇鍝佸悕绉帮級
+        String productName = null;
+        try {
+            MdmItemRespDTO mdmItem = mdmItemApi.getItem(item.getProductId()).getCheckedData();
+            if (mdmItem != null) {
+                productName = mdmItem.getName();
+            }
+        } catch (Exception e) {
+            log.warn("[createMpsForSaleOrderItem] 鑾峰彇 MDM 鐗╂枡淇℃伅澶辫触锛宲roductId={}", item.getProductId(), e);
+        }
+
+        MesProMpsCreateReqDTO createReqDTO = new MesProMpsCreateReqDTO();
+        createReqDTO.setSaleOrderId(saleOrder.getId());
+        createReqDTO.setSaleOrderNo(saleOrder.getNo());
+        createReqDTO.setSaleOrderItemId(item.getId());
+        createReqDTO.setMdmItemId(item.getProductId());
+        createReqDTO.setProductName(productName);
+        createReqDTO.setQuantity(item.getCount());
+        createReqDTO.setClientId(saleOrder.getCustomerId());
+        createReqDTO.setRequestDate(formatRequestDate(saleOrder.getOrderTime()));
+        createReqDTO.setRemark("鑷姩鐢熸垚锛屾潵鑷攢鍞鍗曪細" + saleOrder.getNo());
+
+        Long mpsId = mpsApi.createMps(createReqDTO);
+        log.info("[createMpsForSaleOrderItem] MPS 鍒涘缓鎴愬姛锛宮psId={}, saleOrderItemId={}", mpsId, item.getId());
+        return mpsId;
+    }
+
+    /**
+     * 鏍煎紡鍖栭渶姹傛棩鏈�
+     */
+    private String formatRequestDate(LocalDateTime dateTime) {
+        if (dateTime == null) {
+            return null;
+        }
+        return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
     }
 
     @Override
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOrderStatusListener.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOrderStatusListener.java
deleted file mode 100644
index f068d75..0000000
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/listener/ErpSaleOrderStatusListener.java
+++ /dev/null
@@ -1,160 +0,0 @@
-package cn.iocoder.yudao.module.erp.service.sale.listener;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
-import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
-import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
-import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
-import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderItemMapper;
-import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderMapper;
-import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
-import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
-import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
-import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
-import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsCreateReqDTO;
-import jakarta.annotation.Resource;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Component;
-
-import java.math.BigDecimal;
-import java.time.LocalDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.List;
-
-/**
- * 閿�鍞鍗曞鎵圭姸鎬佺洃鍚櫒
- *
- * 鐩戝惉 BPM 娴佺▼瀹炰緥鐘舵�佸彉鏇翠簨浠讹紝瀹℃牳閫氳繃鏃惰嚜鍔ㄥ垱寤� MPS
- *
- * @author 鑺嬮亾婧愮爜
- */
-@Component
-@Slf4j
-public class ErpSaleOrderStatusListener extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
-
-    /**
-     * 閿�鍞鍗曞鎵规祦绋嬪畾涔� Key
-     */
-    private static final String PROCESS_DEFINITION_KEY = "sale_order_approve";
-
-    @Resource
-    private ErpSaleOrderMapper saleOrderMapper;
-    @Resource
-    private ErpSaleOrderItemMapper saleOrderItemMapper;
-    @Resource
-    private MesProMpsApi mpsApi;
-    @Resource
-    private MdmItemApi mdmItemApi;
-
-    @Override
-    protected String getProcessDefinitionKey() {
-        return PROCESS_DEFINITION_KEY;
-    }
-
-    @Override
-    protected void onEvent(BpmProcessInstanceStatusEvent event) {
-        Long orderId;
-        try {
-            orderId = Long.parseLong(event.getBusinessKey());
-        } catch (NumberFormatException e) {
-            log.warn("[onEvent] businessKey({}) 涓嶆槸鏈夋晥鐨勯攢鍞鍗曠紪鍙�", event.getBusinessKey());
-            return;
-        }
-
-        log.info("[onEvent] 鏀跺埌 BPM 瀹℃壒浜嬩欢锛宱rderId={}, status={}", orderId, event.getStatus());
-
-        // 鍙湁瀹℃壒閫氳繃鎵嶅垱寤� MPS
-        if (!BpmTaskStatusEnum.APPROVE.getStatus().equals(event.getStatus())) {
-            return;
-        }
-
-        try {
-            // 1. 鑾峰彇閿�鍞鍗�
-            ErpSaleOrderDO saleOrder = saleOrderMapper.selectById(orderId);
-            if (saleOrder == null) {
-                log.warn("[onEvent] 閿�鍞鍗曚笉瀛樺湪锛宱rderId={}", orderId);
-                return;
-            }
-
-            // 2. 鑾峰彇閿�鍞鍗曟槑缁�
-            List<ErpSaleOrderItemDO> items = saleOrderItemMapper.selectListByOrderId(orderId);
-            if (CollUtil.isEmpty(items)) {
-                log.info("[onEvent] 閿�鍞鍗�({}) 娌℃湁鏄庣粏锛屾棤闇�鍒涘缓 MPS", orderId);
-                return;
-            }
-
-            // 3. 鏇存柊璁㈠崟鐘舵�佷负宸插鏍�
-            saleOrderMapper.updateById(new ErpSaleOrderDO()
-                    .setId(orderId)
-                    .setStatus(ErpAuditStatus.APPROVE.getStatus()));
-
-            // 4. 閬嶅巻鏄庣粏锛屼负闇�瑕佺敓浜х殑鏄庣粏鍒涘缓 MPS
-            int mpsCount = 0;
-            for (ErpSaleOrderItemDO item : items) {
-                if (item.getNeedProduction() == null || item.getNeedProduction() != 1) {
-                    continue;
-                }
-                try {
-                    Long mpsId = createMpsForSaleOrderItem(saleOrder, item);
-                    if (mpsId != null) {
-                        mpsCount++;
-                        // 鏇存柊閿�鍞鍗曟槑缁嗙殑 MPS 鍏宠仈
-                        saleOrderItemMapper.updateById(new ErpSaleOrderItemDO()
-                                .setId(item.getId())
-                                .setMpsId(mpsId));
-                    }
-                } catch (Exception e) {
-                    log.error("[onEvent] 鍒涘缓 MPS 澶辫触锛宻aleOrderItemId={}", item.getId(), e);
-                }
-            }
-
-            log.info("[onEvent] 閿�鍞鍗�({}) 瀹℃壒瀹屾垚锛屽垱寤� MPS {} 鏉�", orderId, mpsCount);
-        } catch (Exception e) {
-            log.error("[onEvent] 澶勭悊閿�鍞鍗曞鎵逛簨浠跺け璐ワ紝orderId={}", orderId, e);
-        }
-    }
-
-    /**
-     * 涓洪攢鍞鍗曟槑缁嗗垱寤� MPS
-     *
-     * 娉ㄦ剰锛氶攢鍞鍗曟槑缁嗙殑 productId 鏄� MDM 鐗╂枡 ID锛岄渶瑕佹槧灏勫埌 MES 浜у搧
-     */
-    private Long createMpsForSaleOrderItem(ErpSaleOrderDO saleOrder, ErpSaleOrderItemDO item) {
-        // 鑾峰彇 MDM 鐗╂枡淇℃伅锛堢敤浜庤幏鍙栦骇鍝佸悕绉帮級
-        String productName = null;
-        try {
-            MdmItemRespDTO mdmItem = mdmItemApi.getItem(item.getProductId()).getCheckedData();
-            if (mdmItem != null) {
-                productName = mdmItem.getName();
-            }
-        } catch (Exception e) {
-            log.warn("[createMpsForSaleOrderItem] 鑾峰彇 MDM 鐗╂枡淇℃伅澶辫触锛宲roductId={}", item.getProductId(), e);
-        }
-
-        MesProMpsCreateReqDTO createReqDTO = new MesProMpsCreateReqDTO();
-        createReqDTO.setSaleOrderId(saleOrder.getId());
-        createReqDTO.setSaleOrderNo(saleOrder.getNo());
-        createReqDTO.setSaleOrderItemId(item.getId());
-        createReqDTO.setMdmItemId(item.getProductId()); // 閿�鍞鍗曟槑缁嗙殑 productId 鏄� MDM 鐗╂枡 ID
-        createReqDTO.setProductName(productName); // 浜у搧鍚嶇О鐢ㄤ簬閿欒鎻愮ず
-        createReqDTO.setQuantity(item.getCount());
-        createReqDTO.setClientId(saleOrder.getCustomerId());
-        createReqDTO.setRequestDate(formatRequestDate(saleOrder.getOrderTime()));
-        createReqDTO.setRemark("鑷姩鐢熸垚锛屾潵鑷攢鍞鍗曪細" + saleOrder.getNo());
-
-        Long mpsId = mpsApi.createMps(createReqDTO);
-        log.info("[createMpsForSaleOrderItem] MPS 鍒涘缓鎴愬姛锛宮psId={}, saleOrderItemId={}", mpsId, item.getId());
-        return mpsId;
-    }
-
-    /**
-     * 鏍煎紡鍖栭渶姹傛棩鏈�
-     */
-    private String formatRequestDate(LocalDateTime dateTime) {
-        if (dateTime == null) {
-            return null;
-        }
-        return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
-    }
-
-}
\ No newline at end of file
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApi.java
index 6d75372..55de681 100644
--- a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApi.java
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApi.java
@@ -46,4 +46,14 @@
      */
     List<MesProMpsRespDTO> getMpsListBySaleOrderItemIds(List<Long> saleOrderItemIds);
 
+    /**
+     * 妫�鏌ラ攢鍞鍗曟槸鍚﹀彲浠ョ敓鎴愬彂璐ч�氱煡鍗�
+     *
+     * 瑙勫垯锛氬鏋滄湁闇�瑕佺敓浜х殑鏄庣粏锛屽搴旂殑 MPS 蹇呴』宸插畬鎴愶紙status=2锛夋垨宸插彇娑堬紙status=3锛�
+     *
+     * @param saleOrderItemIds 閿�鍞鍗曟槑缁咺D鍒楄〃
+     * @return true=鍙互鐢熸垚鍙戣揣閫氱煡鍗曪紝false=涓嶅彲浠�
+     */
+    Boolean checkCanCreateSalesNotice(List<Long> saleOrderItemIds);
+
 }
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApiImpl.java
index ddd0f82..94c009c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApiImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/mps/MesProMpsApiImpl.java
@@ -1,9 +1,11 @@
 package cn.iocoder.yudao.module.mes.api.mps;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsCreateReqDTO;
 import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsRespDTO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.mps.MesProMpsDO;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProMpsStatusEnum;
 import cn.iocoder.yudao.module.mes.service.pro.mps.MesProMpsService;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
@@ -11,6 +13,7 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 /**
  * MPS API 瀹炵幇绫�
@@ -60,4 +63,36 @@
         return BeanUtils.toBean(mpsList, MesProMpsRespDTO.class);
     }
 
+    @Override
+    public Boolean checkCanCreateSalesNotice(List<Long> saleOrderItemIds) {
+        if (CollUtil.isEmpty(saleOrderItemIds)) {
+            return true;
+        }
+        List<MesProMpsDO> mpsList = mpsService.getMpsListBySaleOrderItemIds(saleOrderItemIds);
+        // 鏋勫缓 saleOrderItemId -> MPS 鐨勬槧灏�
+        Map<Long, MesProMpsDO> mpsMap = new java.util.HashMap<>();
+        for (MesProMpsDO mps : mpsList) {
+            if (mps.getSaleOrderItemId() != null) {
+                mpsMap.put(mps.getSaleOrderItemId(), mps);
+            }
+        }
+        // 妫�鏌ユ瘡涓攢鍞鍗曟槑缁嗭細
+        // - 濡傛灉 MPS 涓嶅瓨鍦細涓嶈兘鐢熸垚鍙戣揣閫氱煡鍗�
+        // - 濡傛灉 MPS 瀛樺湪涓旂姸鎬佷负鑽夌/宸蹭笅鍙戯細涓嶈兘鐢熸垚鍙戣揣閫氱煡鍗�
+        // - 濡傛灉 MPS 瀛樺湪涓旂姸鎬佷负宸插畬鎴�/宸插彇娑堬細鍙互鐢熸垚鍙戣揣閫氱煡鍗�
+        for (Long itemId : saleOrderItemIds) {
+            MesProMpsDO mps = mpsMap.get(itemId);
+            if (mps == null) {
+                // MPS 涓嶅瓨鍦紝涓嶈兘鐢熸垚鍙戣揣閫氱煡鍗�
+                return false;
+            }
+            if (mps.getStatus().equals(MesProMpsStatusEnum.DRAFT.getStatus())
+                    || mps.getStatus().equals(MesProMpsStatusEnum.ISSUED.getStatus())) {
+                // MPS 鏈畬鎴愶紝涓嶈兘鐢熸垚鍙戣揣閫氱煡鍗�
+                return false;
+            }
+        }
+        return true;
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java
index b392411..78f370b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/oqc/MesQcOqcServiceImpl.java
@@ -13,7 +13,6 @@
 import cn.iocoder.yudao.module.mes.enums.qc.MesQcStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.qc.MesQcTypeEnum;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
-import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
 import cn.iocoder.yudao.module.mes.service.qc.defectrecord.MesQcDefectRecordService;
 import cn.iocoder.yudao.module.mes.service.qc.indicatorresult.MesQcIndicatorResultService;
 import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
@@ -57,9 +56,6 @@
     @Resource
     @Lazy
     private MesQcDefectRecordService defectRecordService;
-    @Resource
-    @Lazy
-    private MesMdItemService itemService;
     @Resource
     @Lazy
     private MesWmProductSalesLineService productSalesLineService;
@@ -121,8 +117,8 @@
         // 鏍¢獙缂栧彿鍞竴
         validateOqcCodeUnique(reqVO.getId(), reqVO.getCode());
         // 鏍¢獙鐗╂枡銆佹娴嬩汉鍛樺瓨鍦�
-        // 娉細瀹㈡埛 ID 鏉ヨ嚜 CRM锛屼笉闇�瑕佸湪 MES 涓牎楠�
-        itemService.validateItemExists(reqVO.getItemId());
+        // 娉細鐗╂枡 ID 鏉ヨ嚜 MDM锛屽鎴� ID 鏉ヨ嚜 CRM
+        mdmItemApi.validateItemExists(reqVO.getItemId());
         adminUserApi.validateUser(reqVO.getInspectorUserId());
     }
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
index 17d0dc8..031dc5d 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/transaction/MesWmTransactionServiceImpl.java
@@ -100,12 +100,20 @@
                     reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(),
                     reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime());
         } else {
-            // 鍑哄簱浜嬪姟锛氬簱瀛樿褰曞繀椤诲凡瀛樺湪锛屼笉鍏佽鑷姩鍒涘缓
+            // 鍑哄簱浜嬪姟锛氬簱瀛樿褰曞繀椤诲凡瀛樺湪锛屼笉鍏佽鑷姩鍒涘缓锛堥櫎闈� checkFlag=false 鍏佽璐熷簱瀛橈級
             materialStock = materialStockService.getMaterialStockByCompositeKey(
                     reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(),
                     reqDTO.getBatchId(), reqDTO.getVendorId());
             if (materialStock == null) {
-                throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
+                // checkFlag=false 鏃跺厑璁歌礋搴撳瓨锛岃嚜鍔ㄥ垱寤哄簱瀛樿褰曪紙鏁伴噺灏嗕粠 0 鎵e噺涓鸿礋鏁帮級
+                boolean allowNegative = !ObjUtil.defaultIfNull(reqDTO.getCheckFlag(), true);
+                if (allowNegative) {
+                    materialStock = materialStockService.getOrCreateMaterialStock(
+                            reqDTO.getItemId(), reqDTO.getWarehouseId(), reqDTO.getLocationId(), reqDTO.getAreaId(),
+                            reqDTO.getBatchId(), reqDTO.getBatchCode(), reqDTO.getVendorId(), reqDTO.getReceiptTime());
+                } else {
+                    throw exception(WM_MATERIAL_STOCK_NOT_EXISTS);
+                }
             }
         }
         if (materialStock == null || materialStock.getId() == null) {

--
Gitblit v1.9.3