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 |  244 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 244 insertions(+), 0 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 554b95e..e4b50f5 100644
--- a/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
+++ b/src/main/java/com/ruoyi/production/service/impl/ProductionOrderPickServiceImpl.java
@@ -10,14 +10,20 @@
 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;
@@ -26,6 +32,8 @@
 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;
@@ -53,6 +61,10 @@
     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;
@@ -83,6 +95,7 @@
             // 淇濆瓨棰嗘枡涓昏褰曞揩鐓с��
             ProductionOrderPick orderPick = new ProductionOrderPick();
             orderPick.setProductionOrderId(resolvedDto.getProductionOrderId());
+            orderPick.setProductionOperationTaskId(resolvedDto.getProductionOperationTaskId());
             orderPick.setProductModelId(resolvedDto.getProductModelId());
             orderPick.setBatchNo(storedBatchNo);
             orderPick.setQuantity(resolvedDto.getPickQuantity());
@@ -191,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,
@@ -294,6 +537,7 @@
 
         ProductionOrderPick orderPick = new ProductionOrderPick();
         orderPick.setProductionOrderId(dto.getProductionOrderId());
+        orderPick.setProductionOperationTaskId(dto.getProductionOperationTaskId());
         orderPick.setProductModelId(dto.getProductModelId());
         orderPick.setBatchNo(storedBatchNo);
         orderPick.setQuantity(dto.getPickQuantity());

--
Gitblit v1.9.3