From ac2e2b5af3726ee64dc37c3ebf26989a0346f22a Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期六, 07 十一月 2026 19:16:05 +0800
Subject: [PATCH] feat(feedback): 优化报工审批流程并增加报工人校验

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemconsume/MesWmItemConsumeServiceImpl.java |   76 +++++++++++++++++++++++++++----------
 1 files changed, 55 insertions(+), 21 deletions(-)

diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemconsume/MesWmItemConsumeServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemconsume/MesWmItemConsumeServiceImpl.java
index c5e850f..168fe4a 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemconsume/MesWmItemConsumeServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemconsume/MesWmItemConsumeServiceImpl.java
@@ -14,6 +14,8 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseLocationDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.wm.itemconsume.MesWmItemConsumeMapper;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmItemConsumeStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
@@ -29,17 +31,21 @@
 import jakarta.annotation.Resource;
 import org.springframework.beans.factory.annotation.Autowired;
 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.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
 import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.PRO_FEEDBACK_ROUTE_PROCESS_INVALID;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.WM_ITEM_CONSUME_STOCK_INSUFFICIENT;
 
 /**
  * MES 鐗╂枡娑堣�楄褰� Service 瀹炵幇绫�
@@ -69,10 +75,13 @@
     private MesWmWarehouseAreaService areaService;
     @Resource
     private MesWmMaterialStockService materialStockService;
+    @Resource
+    private MesWmMaterialStockMapper materialStockMapper;
     @Autowired
     private MesMdItemService mesMdItemService;
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public MesWmItemConsumeDO generateItemConsume(MesProFeedbackDO feedback) {
         // 1.1 鏍¢獙宸ュ崟缂栧彿鏈夋晥
         if (feedback.getWorkOrderId() == null) {
@@ -105,9 +114,29 @@
 
         // 3. 鎸夌嚎杈瑰簱 FIFO 鐢熸垚娑堣�楁槑缁�
         List<MesWmItemConsumeDetailDO> allDetails = new ArrayList<>();
+        Map<Long, BigDecimal> allocations = new HashMap<>();
+        // 浠ュ簱瀛� ID 鍥哄畾椤哄簭鍔犻攣锛屽苟閲嶆柊璇诲彇鍐荤粨/鍗犵敤鏁版嵁銆傚鎵圭殑澶栧眰浜嬪姟鎸佺画鎸侀攣鑷虫墸鍑忓畬鎴愩��
+        Map<Long, List<MesWmMaterialStockDO>> stocksByItem = new HashMap<>();
+        for (MesWmItemConsumeLineDO line : lines) {
+            if (stocksByItem.containsKey(line.getItemId())) continue;
+            MesMdItemDO item = mesMdItemService.validateItemExists(line.getItemId());
+            Long stockItemId = item.getMdmItemId() != null ? item.getMdmItemId() : item.getId();
+            MesWmMaterialStockListReqVO query = new MesWmMaterialStockListReqVO().setItemId(stockItemId)
+                    .setWarehouseId(virtualWarehouse.getId()).setLocationId(virtualLocation.getId()).setAreaId(virtualArea.getId());
+            stocksByItem.put(line.getItemId(), materialStockService.getMaterialStockList(query));
+        }
+        Map<Long, MesWmMaterialStockDO> lockedStocks = new HashMap<>();
+        stocksByItem.values().stream().flatMap(List::stream).map(MesWmMaterialStockDO::getId)
+                .filter(java.util.Objects::nonNull).distinct().sorted().forEach(id -> {
+                    MesWmMaterialStockDO locked = materialStockMapper.selectOne(new LambdaQueryWrapperX<MesWmMaterialStockDO>()
+                            .eq(MesWmMaterialStockDO::getId, id).last("FOR UPDATE"));
+                    if (locked != null) lockedStocks.put(id, locked);
+                });
         for (MesWmItemConsumeLineDO line : lines) {
             generateDetailForLine(line, consume.getId(),
-                    virtualWarehouse, virtualLocation, virtualArea, allDetails);
+                    virtualWarehouse, virtualLocation, virtualArea, allDetails, allocations,
+                    stocksByItem.get(line.getItemId()).stream().map(s -> lockedStocks.get(s.getId()))
+                            .filter(java.util.Objects::nonNull).toList());
         }
         if (CollUtil.isNotEmpty(allDetails)) {
             itemConsumeDetailService.createItemConsumeDetailBatch(allDetails);
@@ -121,23 +150,22 @@
      * 瑙勫垯锛�
      * 1. 鏌ヨ璇ョ墿鏂欏湪铏氭嫙绾胯竟搴撲腑鐨勫簱瀛樿褰曪紝鎸夊叆搴撴椂闂村崌搴忥紙FIFO锛�
      * 2. 渚濇浠庡悇鎵规鍒嗛厤锛岀洿鍒版秷鑰楅噺婊¤冻
-     * 3. 濡傛灉绾胯竟搴撴棤搴撳瓨鎴栧簱瀛樹笉瓒筹紝鍓╀綑鏁伴噺鐢熸垚鏃犳壒娆℃槑缁嗭紙寰呭悗缁洏搴撴牳閿�锛�
+     * 3. 鎵i櫎鍐荤粨銆佸崰鐢ㄥ強鏈宸插垎閰嶉噺鍚庝笉瓒虫椂涓鎶ュ伐瀹℃壒
      */
     private void generateDetailForLine(MesWmItemConsumeLineDO line, Long consumeId,
                                        MesWmWarehouseDO warehouse, MesWmWarehouseLocationDO location, MesWmWarehouseAreaDO area,
-                                       List<MesWmItemConsumeDetailDO> allDetails) {
+                                       List<MesWmItemConsumeDetailDO> allDetails, Map<Long, BigDecimal> allocations,
+                                       List<MesWmMaterialStockDO> lockedStocks) {
         // 鏌ヨ璇ョ墿鏂欏湪绾胯竟搴撲腑鐨勫簱瀛橈紙鎸� receiptTime 鍗囧簭 = FIFO锛涘凡鎺掗櫎 quantity = 0锛�
-        MesWmMaterialStockListReqVO stockQuery = new MesWmMaterialStockListReqVO().setItemId(line.getItemId())
-                .setWarehouseId(warehouse.getId()).setLocationId(location.getId()).setAreaId(area.getId());
-        List<MesWmMaterialStockDO> stocks = materialStockService.getMaterialStockList(stockQuery)
-                .stream().filter(s -> s.getQuantity().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
-
-        if (CollUtil.isEmpty(stocks)) {
-            // 绾胯竟搴撴棤璇ョ墿鏂欏簱瀛� 鈫� 鐢熸垚涓嶅甫鎵规鐨勬槑缁�
-            allDetails.add(buildDetail(consumeId, line.getId(), null, line.getItemId(),
-                    line.getQuantity(), null, null, warehouse, location, area));
-            return;
-        }
+        MesMdItemDO item = mesMdItemService.validateItemExists(line.getItemId());
+        Long stockItemId = item.getMdmItemId() != null ? item.getMdmItemId() : item.getId();
+        List<MesWmMaterialStockDO> stocks = lockedStocks
+                .stream().filter(s -> java.util.Objects.equals(s.getItemId(), stockItemId)
+                        && java.util.Objects.equals(s.getWarehouseId(), warehouse.getId())
+                        && java.util.Objects.equals(s.getLocationId(), location.getId())
+                        && java.util.Objects.equals(s.getAreaId(), area.getId())
+                        && !Boolean.TRUE.equals(s.getFrozen()) && s.getQuantity() != null
+                        && s.getQuantity().compareTo(BigDecimal.ZERO) > 0).collect(Collectors.toList());
 
         // 鎸� FIFO 渚濇浠庡悇鎵规鍒嗛厤
         BigDecimal remaining = line.getQuantity();
@@ -145,16 +173,20 @@
             if (remaining.compareTo(BigDecimal.ZERO) <= 0) {
                 break;
             }
-            BigDecimal allocate = stock.getQuantity().min(remaining);
+            BigDecimal available = stock.getQuantity()
+                    .subtract(stock.getFrozenQuantity() == null ? BigDecimal.ZERO : stock.getFrozenQuantity())
+                    .subtract(stock.getReservedQuantity() == null ? BigDecimal.ZERO : stock.getReservedQuantity())
+                    .subtract(allocations.getOrDefault(stock.getId(), BigDecimal.ZERO));
+            if (available.signum() <= 0) continue;
+            BigDecimal allocate = available.min(remaining);
             allDetails.add(buildDetail(consumeId, line.getId(), stock.getId(), line.getItemId(),
                     allocate, stock.getBatchId(), stock.getBatchCode(), warehouse, location, area));
             remaining = remaining.subtract(allocate);
+            allocations.merge(stock.getId(), allocate, BigDecimal::add);
         }
 
-        // 搴撳瓨涓嶈冻鏃讹紝鍓╀綑鏁伴噺鐢熸垚鏃犳壒娆℃槑缁嗭紙寰呭悗缁洏搴撴墜宸ユ牳閿�锛�
         if (remaining.compareTo(BigDecimal.ZERO) > 0) {
-            allDetails.add(buildDetail(consumeId, line.getId(), null, line.getItemId(),
-                    remaining, null, null, warehouse, location, area));
+            throw exception(WM_ITEM_CONSUME_STOCK_INSUFFICIENT, item.getName());
         }
     }
 
@@ -173,6 +205,7 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void finishItemConsume(Long consumeId) {
         // 1. 鏌ヨ娑堣�楁槑缁嗭紙detail 绾у埆锛屽寘鍚簿纭殑鎵规/浠撳簱淇℃伅锛�
         List<MesWmItemConsumeDetailDO> details = itemConsumeDetailService
@@ -180,16 +213,17 @@
 
         // 2. 閬嶅巻鏄庣粏锛屽垱寤哄簱瀛樹簨鍔★紙鎸夋壒娆$簿纭墸鍑忕嚎杈瑰簱锛�
         for (MesWmItemConsumeDetailDO detail : details) {
-            MesMdItemDO item = mesMdItemService.getItem(detail.getItemId());
+            MesMdItemDO item = mesMdItemService.validateItemExists(detail.getItemId());
             wmTransactionService.createTransaction(new MesWmTransactionSaveReqDTO()
                     .setType(MesWmTransactionTypeEnum.OUT.getType())
-                    .setItemId(item.getMdmItemId())
+                    .setItemId(item.getMdmItemId() != null ? item.getMdmItemId() : item.getId())
                     .setQuantity(detail.getQuantity().negate())
                     .setBatchId(detail.getBatchId()).setBatchCode(detail.getBatchCode())
                     .setWarehouseId(detail.getWarehouseId())
                     .setLocationId(detail.getLocationId())
                     .setAreaId(detail.getAreaId())
-                    .setCheckFlag(false) // 绾胯竟搴撳厑璁歌礋搴撳瓨
+                    .setMaterialStockId(detail.getMaterialStockId())
+                    .setCheckFlag(true)
                     .setBizType(MesBizTypeConstants.WM_ITEM_CONSUME)
                     .setBizId(consumeId)
                     .setBizCode("")

--
Gitblit v1.9.3