From 3066a8f5c1f475997bfe23857ba4bae9395900c4 Mon Sep 17 00:00:00 2001
From: yuan <123@>
Date: 星期四, 13 八月 2026 10:19:24 +0800
Subject: [PATCH] fix: 领料功能修改,移除订单领料,添加工序领料功能,支持按工序查询物料与已领料记录

---
 src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java |  512 +++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 461 insertions(+), 51 deletions(-)

diff --git a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
index 478ecd5..e4b50f5 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
@@ -3,23 +3,37 @@
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.enums.ReviewStatusEnum;
 import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
+import com.ruoyi.common.enums.StockOutQualifiedRecordTypeEnum;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.StringUtils;
 import com.ruoyi.production.bean.dto.ProductionOrderPickDto;
 import com.ruoyi.production.bean.vo.ProductionOrderPickVo;
+import com.ruoyi.production.mapper.ProductionBomStructureMapper;
 import com.ruoyi.production.mapper.ProductionOperationTaskMapper;
 import com.ruoyi.production.mapper.ProductionOrderMapper;
 import com.ruoyi.production.mapper.ProductionOrderPickMapper;
 import com.ruoyi.production.mapper.ProductionOrderPickRecordMapper;
+import com.ruoyi.production.mapper.ProductionOrderRoutingOperationMapper;
+import com.ruoyi.production.pojo.ProductionBomStructure;
+import com.ruoyi.production.pojo.ProductionOperationTask;
 import com.ruoyi.production.pojo.ProductionOrder;
 import com.ruoyi.production.pojo.ProductionOrderPick;
 import com.ruoyi.production.pojo.ProductionOrderPickRecord;
+import com.ruoyi.production.pojo.ProductionOrderRoutingOperation;
 import com.ruoyi.production.service.ProductionOrderPickService;
+import com.ruoyi.production.service.ProductionOrderService;
 import com.ruoyi.stock.dto.StockInventoryDto;
 import com.ruoyi.stock.mapper.StockInventoryMapper;
+import com.ruoyi.stock.pojo.StockInRecord;
 import com.ruoyi.stock.pojo.StockInventory;
+import com.ruoyi.stock.pojo.StockOutRecord;
 import com.ruoyi.stock.service.StockInventoryService;
+import com.ruoyi.stock.service.StockInRecordService;
+import com.ruoyi.stock.service.StockOutRecordService;
+import com.ruoyi.technology.mapper.TechnologyOperationMapper;
+import com.ruoyi.technology.pojo.TechnologyOperation;
 import lombok.RequiredArgsConstructor;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -39,12 +53,22 @@
 
     private static final byte PICK_TYPE_NORMAL = 1;
     private static final byte PICK_TYPE_FEEDING = 2;
+    private static final String PICK_STOCK_OUT_RECORD_TYPE = StockOutQualifiedRecordTypeEnum.PICK_STOCK_OUT.getCode();
+    private static final String FEED_STOCK_OUT_RECORD_TYPE = StockOutQualifiedRecordTypeEnum.FEED_STOCK_OUT.getCode();
+    private static final String PICK_RETURN_IN_RECORD_TYPE = StockInQualifiedRecordTypeEnum.PICK_RETURN_IN.getCode();
+    private static final String FEED_RETURN_IN_RECORD_TYPE = StockInQualifiedRecordTypeEnum.FEED_RETURN_IN.getCode();
 
     private final ProductionOrderMapper productionOrderMapper;
     private final ProductionOperationTaskMapper productionOperationTaskMapper;
     private final ProductionOrderPickRecordMapper productionOrderPickRecordMapper;
+    private final ProductionOrderRoutingOperationMapper productionOrderRoutingOperationMapper;
+    private final ProductionBomStructureMapper productionBomStructureMapper;
+    private final ProductionOrderService productionOrderService;
+    private final TechnologyOperationMapper technologyOperationMapper;
     private final StockInventoryMapper stockInventoryMapper;
     private final StockInventoryService stockInventoryService;
+    private final StockInRecordService stockInRecordService;
+    private final StockOutRecordService stockOutRecordService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -52,8 +76,9 @@
         // 棰嗘枡鏂板鎬绘祦绋嬶細
         // 1) 瑙f瀽鍓嶇琛屾暟鎹苟閫愯鍚堝苟鍙傛暟锛�
         // 2) 鏍¢獙鍙傛暟涓庢壒娆★紱
-        // 3) 鍏堟墸鍑忓簱瀛橈紝鍐嶈惤搴撻鏂欎富璁板綍锛�
-        // 4) 鍐欏叆棰嗘枡娴佹按锛岃褰曟暟閲忓彉鍖栬建杩广��
+        // 3) 鍏堜繚瀛橀鏂欎富璁板綍锛�
+        // 4) 鍐嶈蛋鈥滃嚭搴撶敵璇� + 瀹℃壒閫氳繃鈥濆畬鎴愬簱瀛樻墸鍑忥紱
+        // 5) 鍐欏叆棰嗘枡娴佹按锛岃褰曟暟閲忓彉鍖栬建杩广��
         List<ProductionOrderPickDto> pickItems = resolvePickItems(dto);
         // 閫愯澶勭悊棰嗘枡鏁版嵁锛岃鍙风敤浜庢嫾瑁呯簿纭殑鎶ラ敊淇℃伅銆�
         for (int i = 0; i < pickItems.size(); i++) {
@@ -66,11 +91,11 @@
             List<String> batchNoList = resolveBatchNoList(resolvedDto);
             String inventoryBatchNo = pickInventoryBatchNo(batchNoList);
             String storedBatchNo = formatBatchNoStorage(batchNoList);
-            subtractInventory(resolvedDto.getProductModelId(), storedBatchNo, resolvedDto.getPickQuantity(), rowNo);
 
             // 淇濆瓨棰嗘枡涓昏褰曞揩鐓с��
             ProductionOrderPick orderPick = new ProductionOrderPick();
             orderPick.setProductionOrderId(resolvedDto.getProductionOrderId());
+            orderPick.setProductionOperationTaskId(resolvedDto.getProductionOperationTaskId());
             orderPick.setProductModelId(resolvedDto.getProductModelId());
             orderPick.setBatchNo(storedBatchNo);
             orderPick.setQuantity(resolvedDto.getPickQuantity());
@@ -82,6 +107,9 @@
             orderPick.setReturned(false);
             // 鏂板涓昏褰曘��
             baseMapper.insert(orderPick);
+
+            // 鍏堟柊澧炲嚭搴撶敵璇凤紝鍐嶅鎵归�氳繃锛屽畬鎴愬簱瀛樻墸鍑忋��
+            subtractInventory(orderPick.getId(), resolvedDto.getProductModelId(), storedBatchNo, resolvedDto.getPickQuantity(), rowNo, PICK_STOCK_OUT_RECORD_TYPE);
 
             // 璁板綍鏈棰嗘枡娴佹按锛坆efore=0锛宎fter=鏈棰嗘枡閲忥級銆�
             insertPickRecord(orderPick.getId(),
@@ -176,7 +204,237 @@
         List<ProductionOrderPickVo> detailList = baseMapper.listPickedDetailByOrderId(productionOrderId);
         fillBatchNoList(detailList);
         fillSelectableBatchNoList(detailList);
+        fillPickedFlagAndEffectiveQuantity(detailList);
         return detailList;
+    }
+
+    /**
+     * 宸ュ簭鐗╂枡娓呭崟 = 璁㈠崟BOM蹇収涓睘浜庤宸ュ簭鐨勫彲棰嗙敤鐗╂枡寤鸿 + 璇ュ伐鍗曞凡棰嗘枡璁板綍銆�
+     * 寤鸿琛� picked=false锛坕d 涓虹┖锛夛紝鍘嗗彶棰嗘枡琛� picked=true銆�
+     */
+    @Override
+    public List<ProductionOrderPickVo> listTaskMaterials(Long productionOperationTaskId) {
+        if (productionOperationTaskId == null) {
+            return Collections.emptyList();
+        }
+        TaskOperationContext context = resolveTaskOperationContext(productionOperationTaskId);
+
+        List<ProductionOrderPickVo> result = new ArrayList<>();
+        // BOM 寤鸿鐗╂枡锛氭寜褰撳墠宸ュ簭杩囨护锛屼笉甯︿换浣曢渶姹傛暟閲�
+        List<ProductionOrderPickVo> suggestionList =
+                productionOrderService.pick(context.productionOrderId(), context.technologyOperationId());
+        for (ProductionOrderPickVo suggestion : suggestionList) {
+            suggestion.setId(null);
+            suggestion.setPicked(false);
+            suggestion.setProductionOrderId(context.productionOrderId());
+            suggestion.setProductionOperationTaskId(productionOperationTaskId);
+            suggestion.setTechnologyOperationId(context.technologyOperationId());
+            if (StringUtils.isEmpty(suggestion.getOperationName())) {
+                suggestion.setOperationName(context.operationName());
+            }
+            suggestion.setWorkOrderNo(context.workOrderNo());
+            result.add(suggestion);
+        }
+
+        // 璇ュ伐鍗曞凡浜х敓鐨勯鏂欒褰�
+        List<ProductionOrderPickVo> pickedList = baseMapper.listPickedDetailByTaskId(productionOperationTaskId);
+        fillBatchNoList(pickedList);
+        fillSelectableBatchNoList(pickedList);
+        fillPickedFlagAndEffectiveQuantity(pickedList);
+        result.addAll(pickedList);
+        return result;
+    }
+
+    /**
+     * 宸ュ簭缁熶竴棰嗘枡锛�
+     * 1) 鐢卞伐鍗曞弽鏌ヨ鍗曚笌宸ュ簭锛屼笉淇′换鍓嶇浼犲叆鐨勫綊灞炲瓧娈碉紱
+     * 2) 閫愯鏍¢獙鐗╂枡銆佹暟閲忎笌鎵规锛�
+     * 3) 澶嶇敤鍑哄簱鐢宠 + 绔嬪嵆瀹℃壒鐨勫簱瀛樻墸鍑忔祦绋嬶紱
+     * 4) 鍐欏叆棰嗘枡涓昏褰曚笌娴佹按锛岄噸澶嶉鏂欏嵆鏂板璁板綍锛屼笉鍖哄垎鍒濋涓庤ˉ鏂欍��
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Boolean savePickByTask(ProductionOrderPickDto dto) {
+        if (dto == null) {
+            throw new ServiceException("鍙傛暟涓嶈兘涓虹┖");
+        }
+        Long productionOperationTaskId = dto.getProductionOperationTaskId();
+        if (productionOperationTaskId == null) {
+            throw new ServiceException("宸ュ崟ID涓嶈兘涓虹┖");
+        }
+        TaskOperationContext context = resolveTaskOperationContext(productionOperationTaskId);
+
+        List<ProductionOrderPickDto> pickItems = resolvePickItems(dto).stream()
+                .filter(Objects::nonNull)
+                .filter(item -> item.getProductModelId() != null || item.getPickQuantity() != null)
+                .collect(Collectors.toList());
+        if (pickItems.isEmpty()) {
+            throw new ServiceException("璇疯嚦灏戝~鍐欎竴鏉¢鏂欑墿鏂�");
+        }
+
+        for (int i = 0; i < pickItems.size(); i++) {
+            int rowNo = i + 1;
+            ProductionOrderPickDto resolvedDto = mergeDto(dto, pickItems.get(i));
+            if (resolvedDto.getProductModelId() == null) {
+                throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細浜у搧瑙勬牸涓嶈兘涓虹┖");
+            }
+            if (resolvedDto.getPickQuantity() == null
+                    || resolvedDto.getPickQuantity().compareTo(BigDecimal.ZERO) <= 0) {
+                throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細棰嗘枡鏁伴噺蹇呴』澶т簬0");
+            }
+
+            List<String> batchNoList = resolveBatchNoList(resolvedDto);
+            String inventoryBatchNo = pickInventoryBatchNo(batchNoList);
+            String storedBatchNo = formatBatchNoStorage(batchNoList);
+
+            ProductionOrderPick orderPick = new ProductionOrderPick();
+            orderPick.setProductionOrderId(context.productionOrderId());
+            orderPick.setProductionOperationTaskId(productionOperationTaskId);
+            orderPick.setProductModelId(resolvedDto.getProductModelId());
+            orderPick.setBatchNo(storedBatchNo);
+            orderPick.setQuantity(resolvedDto.getPickQuantity());
+            orderPick.setRemark(resolvedDto.getRemark());
+            orderPick.setOperationName(context.operationName());
+            orderPick.setTechnologyOperationId(context.technologyOperationId());
+            // 鎵佸钩BOM涓嶉厤缃暟閲忥紝棰嗘枡涓嶅啀鏈夐渶姹傛暟閲忕害鏉�
+            orderPick.setDemandedQuantity(null);
+            orderPick.setBom(isBomMaterial(context, resolvedDto.getProductModelId()));
+            orderPick.setReturned(false);
+            baseMapper.insert(orderPick);
+
+            // 绔嬪嵆鎵e噺搴撳瓨锛屽け璐ュ垯鏁翠釜浜嬪姟鍥炴粴
+            subtractInventory(orderPick.getId(),
+                    resolvedDto.getProductModelId(),
+                    storedBatchNo,
+                    resolvedDto.getPickQuantity(),
+                    rowNo,
+                    PICK_STOCK_OUT_RECORD_TYPE);
+
+            insertPickRecord(orderPick.getId(),
+                    context.productionOrderId(),
+                    productionOperationTaskId,
+                    resolvedDto.getProductModelId(),
+                    inventoryBatchNo,
+                    resolvedDto.getPickQuantity(),
+                    BigDecimal.ZERO,
+                    resolvedDto.getPickQuantity(),
+                    PICK_TYPE_NORMAL,
+                    resolvedDto.getRemark(),
+                    null);
+        }
+        return true;
+    }
+
+    /**
+     * 褰撳墠宸ュ簭鏄惁宸叉湁鏈夋晥棰嗘枡锛氫换涓�鐗╂枡鐨勫噣棰嗘枡鏁伴噺锛堥鏂�+琛ユ枡-閫�鏂欙級澶т簬0銆�
+     */
+    @Override
+    public boolean hasEffectivePick(Long productionOperationTaskId) {
+        if (productionOperationTaskId == null) {
+            return false;
+        }
+        List<ProductionOrderPick> pickList = baseMapper.selectList(
+                Wrappers.<ProductionOrderPick>lambdaQuery()
+                        .eq(ProductionOrderPick::getProductionOperationTaskId, productionOperationTaskId));
+        for (ProductionOrderPick pick : pickList) {
+            if (pick == null) {
+                continue;
+            }
+            if (resolveEffectiveQuantity(pick).compareTo(BigDecimal.ZERO) > 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 鐢卞伐鍗曡В鏋愬嚭璁㈠崟銆佸伐搴忓揩鐓т笌宸ュ簭鍚嶇О锛屽苟鏍¢獙褰掑睘鍏崇郴銆�
+     */
+    private TaskOperationContext resolveTaskOperationContext(Long productionOperationTaskId) {
+        ProductionOperationTask task = productionOperationTaskMapper.selectById(productionOperationTaskId);
+        if (task == null) {
+            throw new ServiceException("宸ュ崟涓嶅瓨鍦�");
+        }
+        if (task.getProductionOrderId() == null) {
+            throw new ServiceException("宸ュ崟鏈叧鑱旂敓浜ц鍗�");
+        }
+        ProductionOrder productionOrder = productionOrderMapper.selectById(task.getProductionOrderId());
+        if (productionOrder == null) {
+            throw new ServiceException("鐢熶骇璁㈠崟涓嶅瓨鍦�");
+        }
+        ProductionOrderRoutingOperation routingOperation =
+                productionOrderRoutingOperationMapper.selectById(task.getProductionOrderRoutingOperationId());
+        if (routingOperation == null) {
+            throw new ServiceException("宸ュ崟瀵瑰簲鐨勮鍗曞伐搴忎笉瀛樺湪");
+        }
+        if (routingOperation.getProductionOrderId() != null
+                && !Objects.equals(routingOperation.getProductionOrderId(), task.getProductionOrderId())) {
+            throw new ServiceException("宸ュ崟涓庤鍗曞伐搴忓綊灞炰笉涓�鑷�");
+        }
+        if (routingOperation.getTechnologyOperationId() == null) {
+            throw new ServiceException("璁㈠崟宸ュ簭鏈叧鑱斿伐鑹哄伐搴�");
+        }
+        String operationName = routingOperation.getOperationName();
+        if (StringUtils.isEmpty(operationName)) {
+            TechnologyOperation technologyOperation =
+                    technologyOperationMapper.selectById(routingOperation.getTechnologyOperationId());
+            operationName = technologyOperation == null ? null : technologyOperation.getName();
+        }
+        return new TaskOperationContext(
+                task.getId(),
+                task.getProductionOrderId(),
+                routingOperation.getTechnologyOperationId(),
+                operationName,
+                task.getWorkOrderNo());
+    }
+
+    /**
+     * 鍒ゆ柇鐗╂枡鏄惁鏉ヨ嚜璁㈠崟BOM蹇収涓殑褰撳墠宸ュ簭銆�
+     */
+    private boolean isBomMaterial(TaskOperationContext context, Long productModelId) {
+        if (productModelId == null) {
+            return false;
+        }
+        return productionBomStructureMapper.selectCount(
+                Wrappers.<ProductionBomStructure>lambdaQuery()
+                        .eq(ProductionBomStructure::getProductionOrderId, context.productionOrderId())
+                        .eq(ProductionBomStructure::getTechnologyOperationId, context.technologyOperationId())
+                        .eq(ProductionBomStructure::getProductModelId, productModelId)) > 0;
+    }
+
+    private void fillPickedFlagAndEffectiveQuantity(List<ProductionOrderPickVo> detailList) {
+        if (detailList == null || detailList.isEmpty()) {
+            return;
+        }
+        for (ProductionOrderPickVo detail : detailList) {
+            if (detail == null) {
+                continue;
+            }
+            detail.setPicked(detail.getId() != null);
+            detail.setEffectiveQuantity(resolveEffectiveQuantity(detail));
+        }
+    }
+
+    /**
+     * 鍑�棰嗘枡鏁伴噺 = 棰嗘枡鏁伴噺 + 琛ユ枡鏁伴噺 - 閫�鏂欐暟閲忋��
+     */
+    private BigDecimal resolveEffectiveQuantity(ProductionOrderPick pick) {
+        if (pick == null) {
+            return BigDecimal.ZERO;
+        }
+        return defaultDecimal(pick.getQuantity())
+                .add(defaultDecimal(pick.getFeedingQty()))
+                .subtract(defaultDecimal(pick.getReturnQty()));
+    }
+
+    /**
+     * 宸ュ崟瀵瑰簲鐨勯鏂欎笂涓嬫枃锛岄伩鍏嶄緷璧栧墠绔紶鍏ョ殑璁㈠崟/宸ュ簭瀛楁銆�
+     */
+    private record TaskOperationContext(Long productionOperationTaskId,
+                                        Long productionOrderId,
+                                        Long technologyOperationId,
+                                        String operationName,
+                                        String workOrderNo) {
     }
 
     private void processDeletePickIds(ProductionOrderPickDto rootDto,
@@ -201,7 +459,12 @@
             }
             String oldBatchNo = resolveInventoryBatchNoFromStored(existingPick.getBatchNo());
             BigDecimal oldQuantity = defaultDecimal(existingPick.getQuantity());
-            addInventory(existingPick.getProductModelId(), oldBatchNo, oldQuantity);
+            addInventory(existingPick.getId(), existingPick.getProductModelId(), oldBatchNo, oldQuantity, PICK_RETURN_IN_RECORD_TYPE);
+            // 鍒犻櫎鍏宠仈棰嗘枡娴佹按锛岄伩鍏嶉仐鐣欐棤涓昏褰曘��
+            productionOrderPickRecordMapper.delete(
+                    Wrappers.<ProductionOrderPickRecord>lambdaQuery()
+                            .eq(ProductionOrderPickRecord::getPickId, existingPick.getId())
+            );
             int affected = baseMapper.deleteById(deleteId);
             if (affected <= 0) {
                 throw new ServiceException("鍒犻櫎棰嗘枡璁板綍澶辫触锛孖D=" + deleteId);
@@ -240,7 +503,12 @@
         for (ProductionOrderPick missingPick : missingPickList) {
             String oldBatchNo = resolveInventoryBatchNoFromStored(missingPick.getBatchNo());
             BigDecimal oldQuantity = defaultDecimal(missingPick.getQuantity());
-            addInventory(missingPick.getProductModelId(), oldBatchNo, oldQuantity);
+            addInventory(missingPick.getId(), missingPick.getProductModelId(), oldBatchNo, oldQuantity, PICK_RETURN_IN_RECORD_TYPE);
+            // 鍒犻櫎鍏宠仈棰嗘枡娴佹按锛岄伩鍏嶉仐鐣欐棤涓昏褰曘��
+            productionOrderPickRecordMapper.delete(
+                    Wrappers.<ProductionOrderPickRecord>lambdaQuery()
+                            .eq(ProductionOrderPickRecord::getPickId, missingPick.getId())
+            );
             int affected = baseMapper.deleteById(missingPick.getId());
             if (affected <= 0) {
                 throw new ServiceException("鍒犻櫎鏈洖浼犻鏂欒褰曞け璐ワ紝ID=" + missingPick.getId());
@@ -261,14 +529,15 @@
     }
 
     private void addNewPickInUpdate(ProductionOrderPickDto dto, int rowNo) {
-        // 鏇存柊鍦烘櫙涓嬫柊澧炰竴鏉¢鏂欙細鎵e簱瀛� -> 鏂板涓昏褰� -> 鍐欐祦姘淬��
+        // 鏇存柊鍦烘櫙涓嬫柊澧炰竴鏉¢鏂欙細
+        // 鏂板涓昏褰� -> 鍑哄簱鐢宠骞跺鎵� -> 鍐欐祦姘淬��
         List<String> batchNoList = resolveBatchNoList(dto);
         String inventoryBatchNo = pickInventoryBatchNo(batchNoList);
         String storedBatchNo = formatBatchNoStorage(batchNoList);
-        subtractInventory(dto.getProductModelId(), storedBatchNo, dto.getPickQuantity(), rowNo);
 
         ProductionOrderPick orderPick = new ProductionOrderPick();
         orderPick.setProductionOrderId(dto.getProductionOrderId());
+        orderPick.setProductionOperationTaskId(dto.getProductionOperationTaskId());
         orderPick.setProductModelId(dto.getProductModelId());
         orderPick.setBatchNo(storedBatchNo);
         orderPick.setQuantity(dto.getPickQuantity());
@@ -279,6 +548,9 @@
         orderPick.setBom(dto.getBom());
         orderPick.setReturned(false);
         baseMapper.insert(orderPick);
+
+        // 鍏堟柊澧炲嚭搴撶敵璇凤紝鍐嶅鎵归�氳繃锛屽畬鎴愬簱瀛樻墸鍑忋��
+        subtractInventory(orderPick.getId(), dto.getProductModelId(), storedBatchNo, dto.getPickQuantity(), rowNo, PICK_STOCK_OUT_RECORD_TYPE);
 
         insertPickRecord(orderPick.getId(),
                 dto.getProductionOrderId(),
@@ -337,7 +609,7 @@
                 : formatBatchNoStorage(batchNoList);
         BigDecimal feedingQuantity = dto.getFeedingQuantity();
 
-        subtractInventory(productModelId, inventoryBatchNo, feedingQuantity, rowNo);
+        subtractInventory(oldPick.getId(), productModelId, inventoryBatchNo, feedingQuantity, rowNo, FEED_STOCK_OUT_RECORD_TYPE);
 
         // 璁$畻琛ユ枡鍓嶅悗鏁伴噺骞跺啓琛ユ枡娴佹按銆�
         BigDecimal beforeFeedingQty = sumFeedingQuantity(dto.getProductionOrderId(), oldPick.getId());
@@ -393,19 +665,36 @@
     }
 
     private void updateReturnPick(ProductionOrderPickDto dto, ProductionOrderPick oldPick, int rowNo) {
-        // 閫�鏂欐洿鏂板彧鏀逛富棰嗘枡璁板綍涓殑閫�鏂欏瓧娈典笌瀹為檯閲忋��
+        // 閫�鏂欐洿鏂帮細
+        // 1) returnQty 鎸夆�滄湰娆¢��鏂欓噺鈥濆鐞嗭紱
+        // 2) 鏈閫�鏂欓噺鍥炶ˉ鍒扳�滅敓浜ч��鏂欏叆搴撯�濓紱
+        // 3) 绱姞涓昏褰曢��鏂欐�婚噺骞堕噸绠楀疄闄呴噺銆�
+        BigDecimal oldReturnQty = defaultDecimal(oldPick.getReturnQty());
+        BigDecimal currentReturnQty = defaultDecimal(dto.getReturnQty());
+        BigDecimal totalReturnQty = oldReturnQty.add(currentReturnQty);
+        if (currentReturnQty.compareTo(BigDecimal.ZERO) > 0) {
+            String returnBatchNo = resolveInventoryBatchNoFromStored(oldPick.getBatchNo());
+            addInventoryRecordOnly(oldPick.getId(), oldPick.getProductModelId(), returnBatchNo, currentReturnQty, FEED_RETURN_IN_RECORD_TYPE);
+        }
+
+        BigDecimal actualQty = defaultDecimal(oldPick.getQuantity())
+                .add(defaultDecimal(oldPick.getFeedingQty()))
+                .subtract(totalReturnQty);
+        if (actualQty.compareTo(BigDecimal.ZERO) < 0) {
+            throw new ServiceException("绗�" + rowNo + "琛岄��鏂欏け璐ワ細绱閫�鏂欐暟閲忎笉鑳藉ぇ浜庡彲鐢ㄦ暟閲�");
+        }
         ProductionOrderPick updatePick = new ProductionOrderPick();
         updatePick.setId(oldPick.getId());
-        updatePick.setReturnQty(dto.getReturnQty());
-        updatePick.setActualQty(dto.getActualQty());
-        updatePick.setReturned(true);
+        updatePick.setReturnQty(totalReturnQty);
+        updatePick.setActualQty(actualQty);
+        updatePick.setReturned(totalReturnQty.compareTo(BigDecimal.ZERO) > 0);
         int affected = baseMapper.updateById(updatePick);
         if (affected <= 0) {
             throw new ServiceException("绗�" + rowNo + "琛岄��鏂欏け璐ワ細鏇存柊棰嗘枡涓昏褰曞け璐�");
         }
         oldPick.setReturnQty(updatePick.getReturnQty());
         oldPick.setActualQty(updatePick.getActualQty());
-        oldPick.setReturned(true);
+        oldPick.setReturned(updatePick.getReturned());
     }
 
     private void updateExistingPick(ProductionOrderPickDto dto,
@@ -431,21 +720,30 @@
         String newStoredBatchNo = formatBatchNoStorage(newBatchNoList);
         BigDecimal newQuantity = dto.getPickQuantity();
 
-        // 鍒ゆ柇瑙勬牸+鎵规鏄惁鍙樺寲锛屽喅瀹氬簱瀛樺鐞嗙瓥鐣ャ��
+        // 鍒ゆ柇瑙勬牸+鎵规鎴栨暟閲忔槸鍚﹀彉鍖栵紝骞舵寜鍦烘櫙澶勭悊搴撳瓨锛�
+        // 1) 鍚岃鏍煎悓鎵规锛氭寜宸�煎鐞嗭紙澧為噺鎵e噺 / 鍑忛噺鍥為��锛夛紱
+        // 2) 瑙勬牸鎴栨壒娆″彉鍖栵細鍥為��鏃ч鏂欏悗鍐嶉噸鎻愭柊棰嗘枡銆�
         boolean sameStockKey = Objects.equals(oldProductModelId, newProductModelId)
                 && Objects.equals(oldBatchNo, newBatchNo);
+        boolean quantityChanged = oldQuantity.compareTo(newQuantity) != 0;
+        boolean needReissuePickRecord = !sameStockKey || quantityChanged;
         if (sameStockKey) {
-            // 瑙勬牸涓庢壒娆′笉鍙橈細鍙寜宸�煎鍑忓簱瀛樸��
-            BigDecimal delta = newQuantity.subtract(oldQuantity);
-            if (delta.compareTo(BigDecimal.ZERO) > 0) {
-                subtractInventory(newProductModelId, newStoredBatchNo, delta, rowNo);
-            } else if (delta.compareTo(BigDecimal.ZERO) < 0) {
-                addInventory(oldProductModelId, oldBatchNo, delta.abs());
+            BigDecimal deltaQuantity = newQuantity.subtract(oldQuantity);
+            if (deltaQuantity.compareTo(BigDecimal.ZERO) > 0) {
+                // 鏁伴噺澧炲姞锛屽彧鎵e噺鏂板閮ㄥ垎銆�
+                subtractInventory(oldPick.getId(), newProductModelId, newStoredBatchNo, deltaQuantity, rowNo, PICK_STOCK_OUT_RECORD_TYPE);
+            } else if (deltaQuantity.compareTo(BigDecimal.ZERO) < 0) {
+                // 鏁伴噺鍑忓皯锛屽彧鍥為��宸�奸儴鍒嗐��
+                addInventory(oldPick.getId(), oldProductModelId, oldBatchNo, deltaQuantity.abs(), PICK_RETURN_IN_RECORD_TYPE);
             }
         } else {
-            // 瑙勬牸鎴栨壒娆″彉鍖栵細鍏堝洖琛ユ棫搴撳瓨锛屽啀鎵e噺鏂板簱瀛樸��
-            addInventory(oldProductModelId, oldBatchNo, oldQuantity);
-            subtractInventory(newProductModelId, newStoredBatchNo, newQuantity, rowNo);
+            // 瑙勬牸鎴栨壒娆″彉鍖栵細鍏堝叏閲忓洖閫�鏃ч鏂欙紝鍐嶅叏閲忔墸鍑忔柊棰嗘枡銆�
+            addInventory(oldPick.getId(), oldProductModelId, oldBatchNo, oldQuantity, PICK_RETURN_IN_RECORD_TYPE);
+            subtractInventory(oldPick.getId(), newProductModelId, newStoredBatchNo, newQuantity, rowNo, PICK_STOCK_OUT_RECORD_TYPE);
+        }
+        if (needReissuePickRecord) {
+            // 姝e父棰嗘枡娴佹按鎸夆�滄渶鏂伴鏂欓噺鈥濋噸寤猴紝閬垮厤淇濈暀鍘嗗彶鏃у�笺��
+            deleteNormalPickRecord(oldPick.getId());
         }
 
         oldPick.setProductModelId(newProductModelId);
@@ -454,6 +752,9 @@
         oldPick.setRemark(dto.getRemark());
         oldPick.setOperationName(dto.getOperationName());
         oldPick.setTechnologyOperationId(dto.getTechnologyOperationId());
+        // 鏅�氭洿鏂颁篃瑕佸悓姝ラ噸绠楀疄闄呯敤閲忥紝閬垮厤娌跨敤鏃у�笺��
+        // 瑙勫垯锛氬疄闄呯敤閲� = 棰嗘枡鏁伴噺 + 琛ユ枡鏁伴噺 - 閫�鏂欐暟閲忋��
+        oldPick.setActualQty(calculateActualQty(oldPick, oldPick.getFeedingQty()));
         if (dto.getDemandedQuantity() != null) {
             oldPick.setDemandedQuantity(dto.getDemandedQuantity());
         }
@@ -465,16 +766,15 @@
             throw new ServiceException("绗�" + rowNo + "琛屾洿鏂板け璐ワ細鏇存柊棰嗘枡璁板綍澶辫触");
         }
 
-        // 鍐欏叆鏇存柊娴佹按锛屼繚鐣欐湰娆℃暟閲忓彉鍖栬建杩广��
-        BigDecimal recordQuantity = sameStockKey ? oldQuantity.subtract(newQuantity).abs() : newQuantity;
-        if (recordQuantity.compareTo(BigDecimal.ZERO) > 0 || oldQuantity.compareTo(newQuantity) != 0 || !sameStockKey) {
+        // 濡傛灉鍙戠敓棰嗘枡閲嶆彁锛岃ˉ鍐欎竴鏉℃柊鐨勬甯搁鏂欐祦姘淬��
+        if (needReissuePickRecord) {
             insertPickRecord(oldPick.getId(),
                     dto.getProductionOrderId(),
                     dto.getProductionOperationTaskId(),
                     newProductModelId,
                     newBatchNo,
-                    recordQuantity,
-                    oldQuantity,
+                    newQuantity,
+                    BigDecimal.ZERO,
                     newQuantity,
                     dto.getPickType(),
                     dto.getRemark(),
@@ -509,11 +809,28 @@
         productionOrderPickRecordMapper.insert(pickRecord);
     }
 
-    private void subtractInventory(Long productModelId, String batchNo, BigDecimal quantity, int rowNo) {
+    private void deleteNormalPickRecord(Long pickId) {
+        // 鍒犻櫎璇ラ鏂欏崟鍘嗗彶涓婄殑鈥滄甯搁鏂欌�濇祦姘达紝淇濈暀琛ユ枡/閫�鏂欐祦姘淬��
+        if (pickId == null) {
+            return;
+        }
+        productionOrderPickRecordMapper.delete(
+                Wrappers.<ProductionOrderPickRecord>lambdaQuery()
+                        .eq(ProductionOrderPickRecord::getPickId, pickId)
+                        .eq(ProductionOrderPickRecord::getPickType, PICK_TYPE_NORMAL)
+        );
+    }
+
+    private void subtractInventory(Long recordId,
+                                   Long productModelId,
+                                   String batchNo,
+                                   BigDecimal quantity,
+                                   int rowNo,
+                                   String stockOutRecordType) {
         // 鎵e噺搴撳瓨鎬绘祦绋嬶細
         // 1) 瑙f瀽鎵规鍒楄〃锛�
         // 2) 璁$畻姣忎釜鎵规鍙敤閲忎笌鎬诲彲鐢ㄩ噺锛�
-        // 3) 鎸夋壒娆¢『搴忛�愮瑪鎵e噺锛岀洿鍒版墸瀹岀洰鏍囨暟閲忥紱
+        // 3) 鎸夋壒娆¢『搴忛�愮瑪鈥滄柊澧炲嚭搴撹褰曞苟瀹℃壒閫氳繃鈥濓紝鐩村埌鎵e畬鐩爣鏁伴噺锛�
         // 4) 浠讳竴姝ュけ璐ュ嵆鎶涢敊骞跺洖婊氫簨鍔°��
         BigDecimal deductQuantity = defaultDecimal(quantity);
         // 棰嗘枡鏁伴噺灏忎簬绛変簬0鏃讹紝涓嶉渶瑕佹墽琛屽簱瀛樻墸鍑忋��
@@ -562,14 +879,7 @@
                 continue;
             }
             BigDecimal currentDeductQuantity = remainingQuantity.min(availableQuantity);
-            StockInventoryDto stockInventoryDto = new StockInventoryDto();
-            stockInventoryDto.setProductModelId(productModelId);
-            stockInventoryDto.setBatchNo(entry.getKey());
-            stockInventoryDto.setQualitity(currentDeductQuantity);
-            int affected = stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto);
-            if (affected <= 0) {
-                throw new ServiceException("绗�" + rowNo + "琛屾墸鍑忓簱瀛樺け璐ワ細搴撳瓨鏇存柊澶辫触");
-            }
+            createAndApproveStockOutRecord(recordId, productModelId, entry.getKey(), currentDeductQuantity, rowNo, stockOutRecordType);
             remainingQuantity = remainingQuantity.subtract(currentDeductQuantity);
         }
 
@@ -578,19 +888,122 @@
         }
     }
 
-    private void addInventory(Long productModelId, String batchNo, BigDecimal quantity) {
-        // 鍥炶ˉ搴撳瓨锛堢敤浜庡垹闄ら鏂欍�佹敼灏忛鏂欍�佸垏鎹㈡壒娆$瓑鍦烘櫙锛夈��
+    private void createAndApproveStockOutRecord(Long recordId,
+                                                Long productModelId,
+                                                String batchNo,
+                                                BigDecimal quantity,
+                                                int rowNo,
+                                                String stockOutRecordType) {
+        // 搴撳瓨鎵e噺鏀逛负涓ゆ锛�
+        // 1) 鍏堣皟鐢� addStockOutRecordOnly 鏂板寰呭鎵瑰嚭搴撹褰曪紱
+        // 2) 鍐嶈皟鐢ㄥ嚭搴撳鎵癸紝瀹℃壒鐘舵�佸浐瀹氫紶 1锛堥�氳繃锛夈��
+        try {
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setRecordId(recordId == null ? 0L : recordId);
+            stockInventoryDto.setRecordType(stockOutRecordType);
+            stockInventoryDto.setProductModelId(productModelId);
+            stockInventoryDto.setBatchNo(batchNo);
+            stockInventoryDto.setQualitity(quantity);
+            stockInventoryService.addStockOutRecordOnly(stockInventoryDto);
+
+            LambdaQueryWrapper<StockOutRecord> recordWrapper = Wrappers.<StockOutRecord>lambdaQuery()
+                    .eq(StockOutRecord::getRecordId, stockInventoryDto.getRecordId())
+                    .eq(StockOutRecord::getRecordType, stockOutRecordType)
+                    .eq(StockOutRecord::getProductModelId, productModelId)
+                    .eq(StockOutRecord::getType, "0")
+                    .orderByDesc(StockOutRecord::getId)
+                    .last("limit 1");
+            if (StringUtils.isEmpty(batchNo)) {
+                recordWrapper.isNull(StockOutRecord::getBatchNo);
+            } else {
+                recordWrapper.eq(StockOutRecord::getBatchNo, batchNo);
+            }
+            StockOutRecord stockOutRecord = stockOutRecordService.getOne(recordWrapper, false);
+            if (stockOutRecord == null || stockOutRecord.getId() == null) {
+                throw new ServiceException("绗�" + rowNo + "琛屾墸鍑忓簱瀛樺け璐ワ細鏈壘鍒板搴斿嚭搴撶敵璇疯褰�");
+            }
+            stockOutRecordService.batchApprove(
+                    Collections.singletonList(stockOutRecord.getId()),
+                    ReviewStatusEnum.APPROVED.getCode()
+            );
+        } catch (ServiceException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new ServiceException("绗�" + rowNo + "琛屾墸鍑忓簱瀛樺け璐ワ細" + ex.getMessage());
+        }
+    }
+
+    private void addInventory(Long recordId,
+                              Long productModelId,
+                              String batchNo,
+                              BigDecimal quantity,
+                              String stockInRecordType) {
+        // 鍥炶ˉ搴撳瓨鏀逛负涓ゆ锛�
+        // 1) 鍏堟柊澧炲叆搴撶敵璇凤紱
+        // 2) 鍐嶅鎵归�氳繃锛岀‘淇濆簱瀛樼珛鍒诲洖琛ョ敓鏁堛��
         BigDecimal addQuantity = defaultDecimal(quantity);
         if (addQuantity.compareTo(BigDecimal.ZERO) <= 0) {
             return;
         }
-        StockInventoryDto stockInventoryDto = new StockInventoryDto();
-        stockInventoryDto.setProductModelId(productModelId);
-        stockInventoryDto.setBatchNo(batchNo);
-        stockInventoryDto.setQualitity(addQuantity);
-        stockInventoryDto.setRecordType(String.valueOf(StockInQualifiedRecordTypeEnum.PICK_RETURN_IN.getCode()));
-        stockInventoryDto.setRecordId(0L);
-        stockInventoryService.addStockInRecordOnly(stockInventoryDto);
+        try {
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setProductModelId(productModelId);
+            stockInventoryDto.setBatchNo(batchNo);
+            stockInventoryDto.setQualitity(addQuantity);
+            stockInventoryDto.setRecordType(stockInRecordType);
+            stockInventoryDto.setRecordId(recordId == null ? 0L : recordId);
+            stockInventoryService.addStockInRecordOnly(stockInventoryDto);
+
+            LambdaQueryWrapper<StockInRecord> recordWrapper = Wrappers.<StockInRecord>lambdaQuery()
+                    .eq(StockInRecord::getRecordId, stockInventoryDto.getRecordId())
+                    .eq(StockInRecord::getRecordType, stockInventoryDto.getRecordType())
+                    .eq(StockInRecord::getProductModelId, productModelId)
+                    .eq(StockInRecord::getType, "0")
+                    .orderByDesc(StockInRecord::getId)
+                    .last("limit 1");
+            if (StringUtils.isEmpty(batchNo)) {
+                recordWrapper.isNull(StockInRecord::getBatchNo);
+            } else {
+                recordWrapper.eq(StockInRecord::getBatchNo, batchNo);
+            }
+            StockInRecord stockInRecord = stockInRecordService.getOne(recordWrapper, false);
+            if (stockInRecord == null || stockInRecord.getId() == null) {
+                throw new ServiceException("鍥炶ˉ搴撳瓨澶辫触锛氭湭鎵惧埌瀵瑰簲鍏ュ簱鐢宠璁板綍");
+            }
+            stockInRecordService.batchApprove(
+                    Collections.singletonList(stockInRecord.getId()),
+                    ReviewStatusEnum.APPROVED.getCode()
+            );
+        } catch (ServiceException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new ServiceException("鍥炶ˉ搴撳瓨澶辫触锛�" + ex.getMessage());
+        }
+    }
+
+    private void addInventoryRecordOnly(Long recordId,
+                                        Long productModelId,
+                                        String batchNo,
+                                        BigDecimal quantity,
+                                        String stockInRecordType) {
+        // 浠呰褰曞叆搴撶敵璇凤紝涓嶅仛瀹℃牳閫氳繃銆�
+        BigDecimal addQuantity = defaultDecimal(quantity);
+        if (addQuantity.compareTo(BigDecimal.ZERO) <= 0) {
+            return;
+        }
+        try {
+            StockInventoryDto stockInventoryDto = new StockInventoryDto();
+            stockInventoryDto.setProductModelId(productModelId);
+            stockInventoryDto.setBatchNo(batchNo);
+            stockInventoryDto.setQualitity(addQuantity);
+            stockInventoryDto.setRecordType(stockInRecordType);
+            stockInventoryDto.setRecordId(recordId == null ? 0L : recordId);
+            stockInventoryService.addStockInRecordOnly(stockInventoryDto);
+        } catch (ServiceException ex) {
+            throw ex;
+        } catch (Exception ex) {
+            throw new ServiceException("閫�鏂欏叆搴撹褰曚繚瀛樺け璐ワ細" + ex.getMessage());
+        }
     }
 
     private List<ProductionOrderPickDto> resolvePickItems(ProductionOrderPickDto dto) {
@@ -769,7 +1182,7 @@
     }
 
     private void validateReturnParam(ProductionOrderPickDto dto, int rowNo) {
-        // 鏍¢獙閫�鏂欏弬鏁帮紙璁㈠崟銆侀鏂橧D銆侀��鏂欓噺銆佸疄闄呴噺锛夈��
+        // 鏍¢獙閫�鏂欏弬鏁帮紙璁㈠崟銆侀鏂橧D銆侀��鏂欓噺锛夈��
         if (dto.getProductionOrderId() == null) {
             throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細鐢熶骇璁㈠崟ID涓嶈兘涓虹┖");
         }
@@ -778,9 +1191,6 @@
         }
         if (dto.getReturnQty() == null || dto.getReturnQty().compareTo(BigDecimal.ZERO) < 0) {
             throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細閫�鏂欐暟閲忎笉鑳藉皬浜�0");
-        }
-        if (dto.getActualQty() == null || dto.getActualQty().compareTo(BigDecimal.ZERO) < 0) {
-            throw new ServiceException("绗�" + rowNo + "琛屽弬鏁伴敊璇細瀹為檯鏁伴噺涓嶈兘灏忎簬0");
         }
     }
 

--
Gitblit v1.9.3