From eccad5a129106377a275be4f7cdc58e99e9b95d4 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期二, 21 七月 2026 13:45:14 +0800
Subject: [PATCH] feat(mes): 实现库存移库功能

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java |   93 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 93 insertions(+), 0 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
index 34ca046..a707cf5 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
@@ -12,7 +12,10 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.mps.MesProMpsMergeItemDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMapper;
 import cn.iocoder.yudao.module.mes.dal.mysql.pro.mps.MesProMpsMergeItemMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.pro.feedback.MesProFeedbackMapper;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.feedback.MesProFeedbackDO;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProMpsStatusEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
@@ -61,6 +64,8 @@
     private MesMdAutoCodeRecordService autoCodeRecordService;
     @Resource
     private CrmCustomerApi customerApi;
+    @Resource
+    private MesProFeedbackMapper feedbackMapper;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -331,6 +336,94 @@
         mpsMapper.updateProducedQuantity(id, incrQuantity);
     }
 
+    @Override
+    public Boolean checkSaleOrderProductionFinished(Long saleOrderId) {
+        if (saleOrderId == null) {
+            return false;
+        }
+        // 1. 鏌ヨ璇ラ攢鍞鍗曟墍鏈� MPS锛堥�氳繃 saleOrderId 鍏宠仈锛�
+        List<MesProMpsDO> mpsList = mpsMapper.selectList(
+                MesProMpsDO::getSaleOrderId, saleOrderId);
+        if (CollUtil.isEmpty(mpsList)) {
+            // 鏃� MPS 鍏宠仈锛岃涓哄畬鎴�
+            return true;
+        }
+        // 2. 妫�鏌ユ瘡涓� MPS 鏄惁瀹屾垚
+        for (MesProMpsDO mps : mpsList) {
+            if (!isMpsFinished(mps)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public MesProMpsDO getMpsByWorkOrderId(Long workOrderId) {
+        if (workOrderId == null) {
+            return null;
+        }
+        return mpsMapper.selectOne(MesProMpsDO::getWorkOrderId, workOrderId);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean checkAndFinishMps(Long mpsId) {
+        if (mpsId == null) {
+            log.warn("[checkAndFinishMps] mpsId is null");
+            return false;
+        }
+        MesProMpsDO mps = mpsMapper.selectById(mpsId);
+        if (mps == null) {
+            log.warn("[checkAndFinishMps] MPS not found, mpsId={}", mpsId);
+            return false;
+        }
+        // 宸插畬鎴愭垨宸插彇娑堬紝鐩存帴杩斿洖 true
+        if (MesProMpsStatusEnum.FINISHED.getStatus().equals(mps.getStatus())
+                || MesProMpsStatusEnum.CANCELED.getStatus().equals(mps.getStatus())) {
+            log.info("[checkAndFinishMps] MPS {} already finished or canceled, status={}", mpsId, mps.getStatus());
+            return true;
+        }
+        // 妫�鏌ユ槸鍚︽弧瓒冲畬鎴愭潯浠�
+        boolean finished = isMpsFinished(mps);
+        log.info("[checkAndFinishMps] MPS {} check result: {}, quantity={}, produced={}, workOrderId={}",
+                mpsId, finished, mps.getQuantity(), mps.getQuantityProduced(), mps.getWorkOrderId());
+        if (finished) {
+            // 鑷姩瀹屾垚 MPS
+            mpsMapper.updateById(new MesProMpsDO()
+                    .setId(mpsId)
+                    .setStatus(MesProMpsStatusEnum.FINISHED.getStatus())
+                    .setFinishDate(LocalDateTime.now()));
+            log.info("[checkAndFinishMps] MPS {} auto finished", mpsId);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * 鍒ゆ柇鍗曚釜 MPS 鏄惁瀹屾垚
+     */
+    private boolean isMpsFinished(MesProMpsDO mps) {
+        // 1. 宸茬敓浜ф暟閲� >= 璁″垝鏁伴噺
+        BigDecimal quantityProduced = ObjUtil.defaultIfNull(mps.getQuantityProduced(), BigDecimal.ZERO);
+        if (quantityProduced.compareTo(mps.getQuantity()) < 0) {
+            log.debug("[isMpsFinished] MPS {} quantity not enough: produced={}, required={}",
+                    mps.getId(), quantityProduced, mps.getQuantity());
+            return false;
+        }
+        // 2. 妫�鏌ュ伐鍗曚笅鎵�鏈夋姤宸ユ槸鍚﹀畬鎴愶紙纭繚璐ㄦ瀹屾垚锛�
+        if (mps.getWorkOrderId() != null) {
+            Long unfinishedCount = feedbackMapper.selectCount(
+                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesProFeedbackDO>()
+                            .eq(MesProFeedbackDO::getWorkOrderId, mps.getWorkOrderId())
+                            .ne(MesProFeedbackDO::getStatus, MesProFeedbackStatusEnum.FINISHED.getStatus()));
+            log.debug("[isMpsFinished] MPS {} unfinished feedback count: {}", mps.getId(), unfinishedCount);
+            if (unfinishedCount > 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     // ========== 绉佹湁鏂规硶 ==========
 
     /**

--
Gitblit v1.9.3