package com.ruoyi.production.service.impl; 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.StockInQualifiedRecordTypeEnum; 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.ProductionOperationTaskMapper; import com.ruoyi.production.mapper.ProductionOrderMapper; import com.ruoyi.production.mapper.ProductionOrderPickMapper; import com.ruoyi.production.mapper.ProductionOrderPickRecordMapper; import com.ruoyi.production.pojo.ProductionOrder; import com.ruoyi.production.pojo.ProductionOrderPick; import com.ruoyi.production.pojo.ProductionOrderPickRecord; import com.ruoyi.production.service.ProductionOrderPickService; import com.ruoyi.stock.dto.StockInventoryDto; import com.ruoyi.stock.mapper.StockInventoryMapper; import com.ruoyi.stock.pojo.StockInventory; import com.ruoyi.stock.service.StockInventoryService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; /** *

* 璁㈠崟棰嗘枡绾胯竟浠?鏈嶅姟瀹炵幇绫? *

* * @author 鑺杞欢锛堟睙鑻忥級鏈夐檺鍏徃 * @since 2026-04-21 03:55:52 */ @Service @RequiredArgsConstructor public class ProductionOrderPickServiceImpl extends ServiceImpl implements ProductionOrderPickService { private static final byte PICK_TYPE_NORMAL = 1; private static final byte PICK_TYPE_FEEDING = 2; private final ProductionOrderMapper productionOrderMapper; private final ProductionOperationTaskMapper productionOperationTaskMapper; private final ProductionOrderPickRecordMapper productionOrderPickRecordMapper; private final StockInventoryMapper stockInventoryMapper; private final StockInventoryService stockInventoryService; @Override @Transactional(rollbackFor = Exception.class) public Boolean savePick(ProductionOrderPickDto dto) { List pickItems = resolvePickItems(dto); for (int i = 0; i < pickItems.size(); i++) { int rowNo = i + 1; ProductionOrderPickDto resolvedDto = mergeDto(dto, pickItems.get(i)); validatePickParam(resolvedDto, rowNo); List 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.setProductModelId(resolvedDto.getProductModelId()); orderPick.setBatchNo(storedBatchNo); orderPick.setQuantity(resolvedDto.getPickQuantity()); orderPick.setRemark(resolvedDto.getRemark()); orderPick.setOperationName(resolvedDto.getOperationName()); orderPick.setTechnologyOperationId(resolvedDto.getTechnologyOperationId()); orderPick.setDemandedQuantity(resolvedDto.getDemandedQuantity()); orderPick.setBom(resolvedDto.getBom()); orderPick.setReturned(false); baseMapper.insert(orderPick); insertPickRecord(orderPick.getId(), resolvedDto.getProductionOrderId(), resolvedDto.getProductionOperationTaskId(), resolvedDto.getProductModelId(), inventoryBatchNo, resolvedDto.getPickQuantity(), BigDecimal.ZERO, resolvedDto.getPickQuantity(), resolvedDto.getPickType(), resolvedDto.getRemark(), resolvedDto.getFeedingReason()); } return true; } @Override @Transactional(rollbackFor = Exception.class) public Boolean updatePick(ProductionOrderPickDto dto) { if (dto == null) { throw new ServiceException("变更参数不能为空"); } Long productionOrderId = resolveProductionOrderId(dto); if (productionOrderId == null) { throw new ServiceException("生产订单ID不能为空"); } ProductionOrder productionOrder = productionOrderMapper.selectById(productionOrderId); if (productionOrder == null) { throw new ServiceException("生产订单不存在"); } List existingPickList = baseMapper.selectList( Wrappers.lambdaQuery() .eq(ProductionOrderPick::getProductionOrderId, productionOrderId)); Map existingPickMap = existingPickList.stream() .filter(item -> item.getId() != null) .collect(Collectors.toMap(ProductionOrderPick::getId, Function.identity(), (a, b) -> a)); if (isFeedingRequest(dto)) { processFeedingPickItems(dto, existingPickMap, productionOrderId); return true; } if (isReturnRequest(dto)) { processReturnPickItems(dto, existingPickMap, productionOrderId); return true; } processDeletePickIds(dto, existingPickMap, productionOrderId); List pickItems = resolveUpdateItems(dto); Set keepPickIdSet = new HashSet<>(); for (int i = 0; i < pickItems.size(); i++) { int rowNo = i + 1; ProductionOrderPickDto resolvedDto = mergeDto(dto, pickItems.get(i)); if (isEmptyUpdateItem(resolvedDto)) { continue; } if (resolvedDto.getProductionOrderId() == null) { resolvedDto.setProductionOrderId(productionOrderId); } validatePickParam(resolvedDto, rowNo); if (resolvedDto.getId() == null) { addNewPickInUpdate(resolvedDto, rowNo); continue; } keepPickIdSet.add(resolvedDto.getId()); updateExistingPick(resolvedDto, rowNo, existingPickMap); } processMissingPickItems(dto, existingPickMap, productionOrderId, keepPickIdSet); return true; } @Override public List listPickedDetail(Long productionOrderId) { if (productionOrderId == null) { return Collections.emptyList(); } List detailList = baseMapper.listPickedDetailByOrderId(productionOrderId); fillBatchNoList(detailList); fillSelectableBatchNoList(detailList); return detailList; } private void processDeletePickIds(ProductionOrderPickDto rootDto, Map existingPickMap, Long productionOrderId) { if (rootDto.getDeletePickIds() == null || rootDto.getDeletePickIds().isEmpty()) { return; } Set deleteIdSet = new LinkedHashSet<>(rootDto.getDeletePickIds()); for (Long deleteId : deleteIdSet) { if (deleteId == null) { continue; } ProductionOrderPick existingPick = existingPickMap.get(deleteId); if (existingPick == null || !Objects.equals(existingPick.getProductionOrderId(), productionOrderId)) { throw new ServiceException("要删除的领料记录不存在或不属于当前订单,ID=" + deleteId); } String oldBatchNo = resolveInventoryBatchNoFromStored(existingPick.getBatchNo()); BigDecimal oldQuantity = defaultDecimal(existingPick.getQuantity()); addInventory(existingPick.getProductModelId(), oldBatchNo, oldQuantity); int affected = baseMapper.deleteById(deleteId); if (affected <= 0) { throw new ServiceException("删除领料失败,ID=" + deleteId); } insertPickRecord(existingPick.getId(), existingPick.getProductionOrderId(), rootDto.getProductionOperationTaskId(), existingPick.getProductModelId(), oldBatchNo, oldQuantity, oldQuantity, BigDecimal.ZERO, rootDto.getPickType(), rootDto.getRemark(), rootDto.getFeedingReason()); existingPickMap.remove(deleteId); } } private void processMissingPickItems(ProductionOrderPickDto rootDto, Map existingPickMap, Long productionOrderId, Set keepPickIdSet) { if (rootDto.getPickList() == null) { return; } List missingPickList = existingPickMap.values().stream() .filter(Objects::nonNull) .filter(item -> item.getId() != null) .filter(item -> Objects.equals(item.getProductionOrderId(), productionOrderId)) .filter(item -> !keepPickIdSet.contains(item.getId())) .toList(); for (ProductionOrderPick missingPick : missingPickList) { String oldBatchNo = resolveInventoryBatchNoFromStored(missingPick.getBatchNo()); BigDecimal oldQuantity = defaultDecimal(missingPick.getQuantity()); addInventory(missingPick.getProductModelId(), oldBatchNo, oldQuantity); int affected = baseMapper.deleteById(missingPick.getId()); if (affected <= 0) { throw new ServiceException("删除领料失败,ID=" + missingPick.getId()); } insertPickRecord(missingPick.getId(), missingPick.getProductionOrderId(), rootDto.getProductionOperationTaskId(), missingPick.getProductModelId(), oldBatchNo, oldQuantity, oldQuantity, BigDecimal.ZERO, rootDto.getPickType(), rootDto.getRemark(), rootDto.getFeedingReason()); existingPickMap.remove(missingPick.getId()); } } private void addNewPickInUpdate(ProductionOrderPickDto dto, int rowNo) { List 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.setProductModelId(dto.getProductModelId()); orderPick.setBatchNo(storedBatchNo); orderPick.setQuantity(dto.getPickQuantity()); orderPick.setRemark(dto.getRemark()); orderPick.setOperationName(dto.getOperationName()); orderPick.setTechnologyOperationId(dto.getTechnologyOperationId()); orderPick.setDemandedQuantity(dto.getDemandedQuantity()); orderPick.setBom(dto.getBom()); orderPick.setReturned(false); baseMapper.insert(orderPick); insertPickRecord(orderPick.getId(), dto.getProductionOrderId(), dto.getProductionOperationTaskId(), dto.getProductModelId(), inventoryBatchNo, dto.getPickQuantity(), BigDecimal.ZERO, dto.getPickQuantity(), dto.getPickType(), dto.getRemark(), dto.getFeedingReason()); } private void processFeedingPickItems(ProductionOrderPickDto rootDto, Map existingPickMap, Long productionOrderId) { List pickItems = resolveUpdateItems(rootDto); for (int i = 0; i < pickItems.size(); i++) { int rowNo = i + 1; ProductionOrderPickDto resolvedDto = mergeDto(rootDto, pickItems.get(i)); if (isEmptyUpdateItem(resolvedDto)) { continue; } if (!isFeedingPick(resolvedDto)) { throw new ServiceException("补料请求中的领料类型必须全部为2"); } if (resolvedDto.getProductionOrderId() == null) { resolvedDto.setProductionOrderId(productionOrderId); } validateFeedingParam(resolvedDto, rowNo); ProductionOrderPick oldPick = existingPickMap.get(resolvedDto.getId()); if (oldPick == null || !Objects.equals(oldPick.getProductionOrderId(), productionOrderId)) { throw new ServiceException("第" + rowNo + "条领料记录不存在或不属于当前订单"); } addFeedingPick(resolvedDto, oldPick, rowNo); } } private void addFeedingPick(ProductionOrderPickDto dto, ProductionOrderPick oldPick, int rowNo) { if (dto.getProductModelId() != null && !Objects.equals(dto.getProductModelId(), oldPick.getProductModelId())) { throw new ServiceException("第" + rowNo + "条补料产品规格与领料记录不一致"); } Long productModelId = oldPick.getProductModelId(); List batchNoList = resolveBatchNoList(dto); String inventoryBatchNo = batchNoList.isEmpty() ? resolveInventoryBatchNoFromStored(oldPick.getBatchNo()) : formatBatchNoStorage(batchNoList); BigDecimal feedingQuantity = dto.getFeedingQuantity(); subtractInventory(productModelId, inventoryBatchNo, feedingQuantity, rowNo); BigDecimal beforeFeedingQty = sumFeedingQuantity(dto.getProductionOrderId(), oldPick.getId()); BigDecimal afterFeedingQty = beforeFeedingQty.add(feedingQuantity); insertPickRecord(oldPick.getId(), dto.getProductionOrderId(), dto.getProductionOperationTaskId(), productModelId, inventoryBatchNo, feedingQuantity, beforeFeedingQty, afterFeedingQty, PICK_TYPE_FEEDING, dto.getRemark(), dto.getFeedingReason()); ProductionOrderPick updatePick = new ProductionOrderPick(); updatePick.setId(oldPick.getId()); updatePick.setFeedingQty(afterFeedingQty); updatePick.setActualQty(calculateActualQty(oldPick, afterFeedingQty)); int affected = baseMapper.updateById(updatePick); if (affected <= 0) { throw new ServiceException("第" + rowNo + "条补料总量更新失败"); } oldPick.setFeedingQty(afterFeedingQty); oldPick.setActualQty(updatePick.getActualQty()); } private void processReturnPickItems(ProductionOrderPickDto rootDto, Map existingPickMap, Long productionOrderId) { List pickItems = resolveUpdateItems(rootDto); for (int i = 0; i < pickItems.size(); i++) { int rowNo = i + 1; ProductionOrderPickDto resolvedDto = mergeDto(rootDto, pickItems.get(i)); if (isEmptyUpdateItem(resolvedDto)) { continue; } if (resolvedDto.getProductionOrderId() == null) { resolvedDto.setProductionOrderId(productionOrderId); } validateReturnParam(resolvedDto, rowNo); ProductionOrderPick oldPick = existingPickMap.get(resolvedDto.getId()); if (oldPick == null || !Objects.equals(oldPick.getProductionOrderId(), productionOrderId)) { throw new ServiceException("第" + rowNo + "条领料记录不存在或不属于当前订单"); } updateReturnPick(resolvedDto, oldPick, rowNo); } } private void updateReturnPick(ProductionOrderPickDto dto, ProductionOrderPick oldPick, int rowNo) { ProductionOrderPick updatePick = new ProductionOrderPick(); updatePick.setId(oldPick.getId()); updatePick.setReturnQty(dto.getReturnQty()); updatePick.setActualQty(dto.getActualQty()); updatePick.setReturned(true); int affected = baseMapper.updateById(updatePick); if (affected <= 0) { throw new ServiceException("第" + rowNo + "条退料信息更新失败"); } oldPick.setReturnQty(updatePick.getReturnQty()); oldPick.setActualQty(updatePick.getActualQty()); oldPick.setReturned(true); } private void updateExistingPick(ProductionOrderPickDto dto, int rowNo, Map existingPickMap) { ProductionOrderPick oldPick = existingPickMap.get(dto.getId()); if (oldPick == null || !Objects.equals(oldPick.getProductionOrderId(), dto.getProductionOrderId())) { throw new ServiceException("第" + rowNo + "条领料记录不存在或不属于当前订单"); } Long oldProductModelId = oldPick.getProductModelId(); String oldBatchNo = resolveInventoryBatchNoFromStored(oldPick.getBatchNo()); BigDecimal oldQuantity = defaultDecimal(oldPick.getQuantity()); Long newProductModelId = dto.getProductModelId(); List newBatchNoList = resolveBatchNoList(dto); String newBatchNo = pickInventoryBatchNo(newBatchNoList); String newStoredBatchNo = formatBatchNoStorage(newBatchNoList); BigDecimal newQuantity = dto.getPickQuantity(); boolean sameStockKey = Objects.equals(oldProductModelId, newProductModelId) && Objects.equals(oldBatchNo, newBatchNo); 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()); } } else { addInventory(oldProductModelId, oldBatchNo, oldQuantity); subtractInventory(newProductModelId, newStoredBatchNo, newQuantity, rowNo); } oldPick.setProductModelId(newProductModelId); oldPick.setBatchNo(newStoredBatchNo); oldPick.setQuantity(newQuantity); oldPick.setRemark(dto.getRemark()); oldPick.setOperationName(dto.getOperationName()); oldPick.setTechnologyOperationId(dto.getTechnologyOperationId()); if (dto.getDemandedQuantity() != null) { oldPick.setDemandedQuantity(dto.getDemandedQuantity()); } if (dto.getBom() != null) { oldPick.setBom(dto.getBom()); } int affected = baseMapper.updateById(oldPick); if (affected <= 0) { throw new ServiceException("第" + rowNo + "条领料更新失败"); } BigDecimal recordQuantity = sameStockKey ? oldQuantity.subtract(newQuantity).abs() : newQuantity; if (recordQuantity.compareTo(BigDecimal.ZERO) > 0 || oldQuantity.compareTo(newQuantity) != 0 || !sameStockKey) { insertPickRecord(oldPick.getId(), dto.getProductionOrderId(), dto.getProductionOperationTaskId(), newProductModelId, newBatchNo, recordQuantity, oldQuantity, newQuantity, dto.getPickType(), dto.getRemark(), dto.getFeedingReason()); } } private void insertPickRecord(Long pickId, Long productionOrderId, Long productionOperationTaskId, Long productModelId, String batchNo, BigDecimal pickQuantity, BigDecimal beforeQuantity, BigDecimal afterQuantity, Byte pickType, String remark, String feedingReason) { ProductionOrderPickRecord pickRecord = new ProductionOrderPickRecord(); pickRecord.setPickId(pickId); pickRecord.setProductionOrderId(productionOrderId); pickRecord.setProductionOperationTaskId(productionOperationTaskId); pickRecord.setProductModelId(productModelId); pickRecord.setBatchNo(batchNo); pickRecord.setPickQuantity(defaultDecimal(pickQuantity)); pickRecord.setBeforeQuantity(defaultDecimal(beforeQuantity)); pickRecord.setAfterQuantity(defaultDecimal(afterQuantity)); pickRecord.setPickType(pickType == null ? PICK_TYPE_NORMAL : pickType); pickRecord.setRemark(remark); pickRecord.setFeedingReason(feedingReason); productionOrderPickRecordMapper.insert(pickRecord); } private void subtractInventory(Long productModelId, String batchNo, BigDecimal quantity, int rowNo) { BigDecimal deductQuantity = defaultDecimal(quantity); if (deductQuantity.compareTo(BigDecimal.ZERO) <= 0) { return; } List batchNoList = parseBatchNoValue(batchNo); if (batchNoList.isEmpty()) { batchNoList = Collections.singletonList(null); } Map availableQuantityMap = new LinkedHashMap<>(); BigDecimal totalAvailableQuantity = BigDecimal.ZERO; for (String currentBatchNo : batchNoList) { StockInventory stockInventory = stockInventoryMapper.selectOne(buildStockWrapper(productModelId, currentBatchNo)); BigDecimal availableQuantity = BigDecimal.ZERO; if (stockInventory != null) { availableQuantity = defaultDecimal(stockInventory.getQualitity()) .subtract(defaultDecimal(stockInventory.getLockedQuantity())); if (availableQuantity.compareTo(BigDecimal.ZERO) < 0) { availableQuantity = BigDecimal.ZERO; } } availableQuantityMap.put(currentBatchNo, availableQuantity); totalAvailableQuantity = totalAvailableQuantity.add(availableQuantity); } if (deductQuantity.compareTo(totalAvailableQuantity) > 0) { BigDecimal shortQuantity = deductQuantity.subtract(totalAvailableQuantity); throw new ServiceException("领料可用库存不足,可用库存为" + formatQuantity(totalAvailableQuantity) + ",还差" + formatQuantity(shortQuantity)); } BigDecimal remainingQuantity = deductQuantity; for (Map.Entry entry : availableQuantityMap.entrySet()) { if (remainingQuantity.compareTo(BigDecimal.ZERO) <= 0) { break; } BigDecimal availableQuantity = defaultDecimal(entry.getValue()); if (availableQuantity.compareTo(BigDecimal.ZERO) <= 0) { 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 + "条领料扣减库存失败"); } remainingQuantity = remainingQuantity.subtract(currentDeductQuantity); } if (remainingQuantity.compareTo(BigDecimal.ZERO) > 0) { throw new ServiceException("第" + rowNo + "条领料扣减库存失败,剩余待扣减数量为" + formatQuantity(remainingQuantity)); } } private void addInventory(Long productModelId, String batchNo, BigDecimal quantity) { 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); } private List resolvePickItems(ProductionOrderPickDto dto) { if (dto == null) { throw new ServiceException("领料参数不能为空"); } if (dto.getPickList() != null && !dto.getPickList().isEmpty()) { return dto.getPickList(); } return Collections.singletonList(dto); } private List resolveUpdateItems(ProductionOrderPickDto dto) { if (dto.getPickList() != null) { return dto.getPickList(); } if (isEmptyUpdateItem(dto)) { return Collections.emptyList(); } return Collections.singletonList(dto); } private boolean isEmptyUpdateItem(ProductionOrderPickDto dto) { return dto.getId() == null && dto.getProductModelId() == null && dto.getPickQuantity() == null && StringUtils.isEmpty(dto.getBatchNo()) && (dto.getBatchNoList() == null || dto.getBatchNoList().isEmpty()) && dto.getPickType() == null && dto.getFeedingQuantity() == null && StringUtils.isEmpty(dto.getFeedingReason()) && dto.getReturnQty() == null && dto.getActualQty() == null && dto.getReturned() == null && dto.getProductionOperationTaskId() == null && dto.getTechnologyOperationId() == null && StringUtils.isEmpty(dto.getOperationName()) && dto.getDemandedQuantity() == null && dto.getBom() == null && StringUtils.isEmpty(dto.getRemark()); } private Long resolveProductionOrderId(ProductionOrderPickDto dto) { if (dto.getProductionOrderId() != null) { return dto.getProductionOrderId(); } if (dto.getPickList() == null || dto.getPickList().isEmpty()) { return null; } return dto.getPickList().stream() .filter(Objects::nonNull) .map(ProductionOrderPickDto::getProductionOrderId) .filter(Objects::nonNull) .findFirst() .orElse(null); } private ProductionOrderPickDto mergeDto(ProductionOrderPickDto rootDto, ProductionOrderPickDto itemDto) { ProductionOrderPickDto merged = new ProductionOrderPickDto(); if (itemDto != null) { merged.setId(itemDto.getId()); merged.setProductionOrderId(itemDto.getProductionOrderId()); merged.setProductionOperationTaskId(itemDto.getProductionOperationTaskId()); merged.setProductModelId(itemDto.getProductModelId()); merged.setBatchNo(itemDto.getBatchNo()); merged.setBatchNoList(itemDto.getBatchNoList()); merged.setPickQuantity(itemDto.getPickQuantity()); merged.setPickType(itemDto.getPickType()); merged.setRemark(itemDto.getRemark()); merged.setFeedingReason(itemDto.getFeedingReason()); merged.setFeedingQuantity(itemDto.getFeedingQuantity()); merged.setTechnologyOperationId(itemDto.getTechnologyOperationId()); merged.setOperationName(itemDto.getOperationName()); merged.setDemandedQuantity(itemDto.getDemandedQuantity()); merged.setBom(itemDto.getBom()); merged.setReturnQty(itemDto.getReturnQty()); merged.setActualQty(itemDto.getActualQty()); merged.setReturned(itemDto.getReturned()); } if (merged.getId() == null) { merged.setId(rootDto.getId()); } if (merged.getProductionOrderId() == null) { merged.setProductionOrderId(rootDto.getProductionOrderId()); } if (merged.getProductionOperationTaskId() == null) { merged.setProductionOperationTaskId(rootDto.getProductionOperationTaskId()); } if (merged.getProductModelId() == null) { merged.setProductModelId(rootDto.getProductModelId()); } if (merged.getBatchNo() == null) { merged.setBatchNo(rootDto.getBatchNo()); } if (merged.getBatchNoList() == null || merged.getBatchNoList().isEmpty()) { merged.setBatchNoList(rootDto.getBatchNoList()); } if (merged.getPickQuantity() == null) { merged.setPickQuantity(rootDto.getPickQuantity()); } if (merged.getPickType() == null) { merged.setPickType(rootDto.getPickType()); } if (merged.getRemark() == null) { merged.setRemark(rootDto.getRemark()); } if (merged.getFeedingReason() == null) { merged.setFeedingReason(rootDto.getFeedingReason()); } if (merged.getFeedingQuantity() == null) { merged.setFeedingQuantity(rootDto.getFeedingQuantity()); } if (merged.getTechnologyOperationId() == null) { merged.setTechnologyOperationId(rootDto.getTechnologyOperationId()); } if (merged.getOperationName() == null) { merged.setOperationName(rootDto.getOperationName()); } if (merged.getDemandedQuantity() == null) { merged.setDemandedQuantity(rootDto.getDemandedQuantity()); } if (merged.getBom() == null) { merged.setBom(rootDto.getBom()); } if (merged.getReturnQty() == null) { merged.setReturnQty(rootDto.getReturnQty()); } if (merged.getActualQty() == null) { merged.setActualQty(rootDto.getActualQty()); } if (merged.getReturned() == null) { merged.setReturned(rootDto.getReturned()); } return merged; } private void validatePickParam(ProductionOrderPickDto dto, int rowNo) { if (dto.getProductionOrderId() == null) { throw new ServiceException("第" + rowNo + "条生产订单ID不能为空"); } if (dto.getProductModelId() == null) { throw new ServiceException("第" + rowNo + "条产品规格ID不能为空"); } if (dto.getPickQuantity() == null || dto.getPickQuantity().compareTo(BigDecimal.ZERO) < 0) { throw new ServiceException("第" + rowNo + "条领料数量不能小于0"); } if (dto.getPickType() != null && dto.getPickType() != PICK_TYPE_NORMAL && dto.getPickType() != PICK_TYPE_FEEDING) { throw new ServiceException("第" + rowNo + "条领料类型只能是1或2"); } } private void validateFeedingParam(ProductionOrderPickDto dto, int rowNo) { if (dto.getProductionOrderId() == null) { throw new ServiceException("第" + rowNo + "条生产订单ID不能为空"); } if (dto.getId() == null) { throw new ServiceException("第" + rowNo + "条领料ID不能为空"); } if (dto.getFeedingQuantity() == null || dto.getFeedingQuantity().compareTo(BigDecimal.ZERO) < 0) { throw new ServiceException("第" + rowNo + "条本次补料数量不能小于0"); } if (!isFeedingPick(dto)) { throw new ServiceException("第" + rowNo + "条补料类型必须为2"); } } private void validateReturnParam(ProductionOrderPickDto dto, int rowNo) { if (dto.getProductionOrderId() == null) { throw new ServiceException("第" + rowNo + "条生产订单ID不能为空"); } if (dto.getId() == null) { throw new ServiceException("第" + rowNo + "条领料ID不能为空"); } 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"); } } private boolean isFeedingRequest(ProductionOrderPickDto dto) { if (isFeedingPick(dto)) { return true; } if (dto.getPickList() == null || dto.getPickList().isEmpty()) { return false; } return dto.getPickList().stream() .filter(Objects::nonNull) .anyMatch(this::isFeedingPick); } private boolean isFeedingPick(ProductionOrderPickDto dto) { return dto != null && Objects.equals(dto.getPickType(), PICK_TYPE_FEEDING); } private boolean isReturnRequest(ProductionOrderPickDto dto) { if (isReturnPick(dto)) { return true; } if (dto.getPickList() == null || dto.getPickList().isEmpty()) { return false; } return dto.getPickList().stream() .filter(Objects::nonNull) .anyMatch(this::isReturnPick); } private boolean isReturnPick(ProductionOrderPickDto dto) { return dto != null && Boolean.TRUE.equals(dto.getReturned()); } private BigDecimal sumFeedingQuantity(Long productionOrderId, Long pickId) { List feedingRecords = productionOrderPickRecordMapper.selectList( Wrappers.lambdaQuery() .eq(ProductionOrderPickRecord::getProductionOrderId, productionOrderId) .eq(ProductionOrderPickRecord::getPickId, pickId) .eq(ProductionOrderPickRecord::getPickType, PICK_TYPE_FEEDING)); return feedingRecords.stream() .map(ProductionOrderPickRecord::getPickQuantity) .map(this::defaultDecimal) .reduce(BigDecimal.ZERO, BigDecimal::add); } private BigDecimal calculateActualQty(ProductionOrderPick pick, BigDecimal feedingQty) { return defaultDecimal(pick.getQuantity()) .add(defaultDecimal(feedingQty)) .subtract(defaultDecimal(pick.getReturnQty())); } private String normalizeBatchNo(String batchNo) { if (StringUtils.isEmpty(batchNo)) { return null; } String trimBatchNo = batchNo.trim(); return trimBatchNo.isEmpty() ? null : trimBatchNo; } private List resolveBatchNoList(ProductionOrderPickDto dto) { List normalizedBatchNoList = normalizeBatchNoList(dto.getBatchNoList()); if (!normalizedBatchNoList.isEmpty()) { return normalizedBatchNoList; } return parseBatchNoValue(dto.getBatchNo()); } private String pickInventoryBatchNo(List batchNoList) { if (batchNoList == null || batchNoList.isEmpty()) { return null; } return batchNoList.get(0); } private String resolveInventoryBatchNoFromStored(String storedBatchNo) { return pickInventoryBatchNo(parseBatchNoValue(storedBatchNo)); } private String formatBatchNoStorage(List batchNoList) { if (batchNoList == null || batchNoList.isEmpty()) { return null; } if (batchNoList.size() == 1) { return batchNoList.get(0); } return String.join(",", batchNoList); } private List normalizeBatchNoList(List batchNoList) { if (batchNoList == null || batchNoList.isEmpty()) { return Collections.emptyList(); } LinkedHashSet normalizedSet = new LinkedHashSet<>(); for (String batchNo : batchNoList) { String normalizedBatchNo = normalizeBatchNo(batchNo); if (!StringUtils.isEmpty(normalizedBatchNo)) { normalizedSet.add(normalizedBatchNo); } } return new ArrayList<>(normalizedSet); } private void fillBatchNoList(List detailList) { if (detailList == null || detailList.isEmpty()) { return; } Map> batchNoGroupMap = new HashMap<>(); for (ProductionOrderPickVo detail : detailList) { String key = buildBatchNoGroupKey(detail); LinkedHashSet batchSet = batchNoGroupMap.computeIfAbsent(key, k -> new LinkedHashSet<>()); batchSet.addAll(parseBatchNoValue(detail.getBatchNo())); if (detail.getBatchNoList() != null && !detail.getBatchNoList().isEmpty()) { batchSet.addAll(normalizeBatchNoList(detail.getBatchNoList())); } } for (ProductionOrderPickVo detail : detailList) { String key = buildBatchNoGroupKey(detail); LinkedHashSet batchSet = batchNoGroupMap.get(key); detail.setBatchNoList(batchSet == null ? Collections.emptyList() : new ArrayList<>(batchSet)); } } private void fillSelectableBatchNoList(List detailList) { if (detailList == null || detailList.isEmpty()) { return; } Set productModelIdSet = detailList.stream() .map(ProductionOrderPickVo::getProductModelId) .filter(Objects::nonNull) .collect(Collectors.toSet()); if (productModelIdSet.isEmpty()) { return; } List stockBatchList = stockInventoryMapper.listSelectableBatchNoByProductModelIds( new ArrayList<>(productModelIdSet)); Map> stockBatchMap = new HashMap<>(); for (StockInventory stockInventory : stockBatchList) { if (stockInventory == null || stockInventory.getProductModelId() == null) { continue; } String normalizedBatchNo = normalizeBatchNo(stockInventory.getBatchNo()); if (StringUtils.isEmpty(normalizedBatchNo)) { continue; } stockBatchMap.computeIfAbsent(stockInventory.getProductModelId(), k -> new LinkedHashSet<>()) .add(normalizedBatchNo); } for (ProductionOrderPickVo detail : detailList) { LinkedHashSet mergedBatchSet = new LinkedHashSet<>(); mergedBatchSet.addAll(normalizeBatchNoList(detail.getBatchNoList())); LinkedHashSet selectableBatchSet = stockBatchMap.get(detail.getProductModelId()); if (selectableBatchSet != null) { mergedBatchSet.addAll(selectableBatchSet); } detail.setBatchNoList(new ArrayList<>(mergedBatchSet)); } } private String buildBatchNoGroupKey(ProductionOrderPickVo detail) { return String.valueOf(detail.getProductionOrderId()) + "|" + String.valueOf(detail.getProductModelId()) + "|" + String.valueOf(detail.getTechnologyOperationId()) + "|" + String.valueOf(detail.getOperationName()); } private List parseBatchNoValue(String rawBatchNoValue) { String normalizedValue = normalizeBatchNo(rawBatchNoValue); if (StringUtils.isEmpty(normalizedValue)) { return Collections.emptyList(); } if (normalizedValue.startsWith("[") && normalizedValue.endsWith("]")) { String value = normalizedValue.substring(1, normalizedValue.length() - 1); if (StringUtils.isEmpty(value)) { return Collections.emptyList(); } List parsed = Arrays.stream(value.split(",")) .map(item -> item == null ? null : item.trim().replace("\"", "").replace("'", "")) .collect(Collectors.toList()); return normalizeBatchNoList(parsed); } if (normalizedValue.contains(",")) { List parsed = Arrays.stream(normalizedValue.split(",")) .map(item -> item == null ? null : item.trim()) .collect(Collectors.toList()); return normalizeBatchNoList(parsed); } return Collections.singletonList(normalizedValue); } private LambdaQueryWrapper buildStockWrapper(Long productModelId, String batchNo) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery() .eq(StockInventory::getProductModelId, productModelId); if (StringUtils.isEmpty(batchNo)) { wrapper.isNull(StockInventory::getBatchNo); } else { wrapper.eq(StockInventory::getBatchNo, batchNo); } return wrapper; } private BigDecimal defaultDecimal(BigDecimal value) { return value == null ? BigDecimal.ZERO : value; } private String formatQuantity(BigDecimal value) { return defaultDecimal(value).stripTrailingZeros().toPlainString(); } }