liding
6 天以前 4858d6a68446a5153749eca8fae8ae099ac879a2
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -1,91 +1,635 @@
package com.ruoyi.stock.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.approve.service.impl.ApproveProcessServiceImpl;
import com.ruoyi.approve.vo.ApproveProcessVO;
import com.ruoyi.basic.mapper.ProductMapper;
import com.ruoyi.basic.mapper.ProductModelMapper;
import com.ruoyi.basic.pojo.Product;
import com.ruoyi.basic.pojo.ProductModel;
import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum;
import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.framework.security.LoginUser;
import com.ruoyi.framework.web.domain.R;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.dto.StockOutRecordDto;
import com.ruoyi.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.stock.dto.*;
import com.ruoyi.stock.execl.FinishedProductInventoryExportData;
import com.ruoyi.stock.execl.NonFinishedProductInventoryExportData;
import com.ruoyi.stock.execl.StockInventoryExportData;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.pojo.StockInRecord;
import com.ruoyi.stock.pojo.StockInventory;
import com.ruoyi.stock.mapper.StockInventoryMapper;
import com.ruoyi.stock.service.StockInRecordService;
import com.ruoyi.stock.service.StockInventoryService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.stock.service.StockOutRecordService;
import com.ruoyi.stock.service.StockUninventoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
/**
 * <p>
 * 库存表 服务实现类
 * </p>
 *
 * @author 芯导软件(江苏)有限公司
 * @since 2026-01-21 04:16:36
 */
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@Service
public class StockInventoryServiceImpl extends ServiceImpl<StockInventoryMapper, StockInventory> implements StockInventoryService {
    @Autowired
    private  StockInventoryMapper stockInventoryMapper;
    private StockInventoryMapper stockInventoryMapper;
    @Autowired
    private StockInRecordService stockInRecordService;
    @Autowired
    private StockOutRecordService stockOutRecordService;
    @Autowired
    private SalesLedgerProductMapper salesLedgerProductMapper;
    @Autowired
    private ApproveProcessServiceImpl approveProcessService;
    @Autowired
    private ProductModelMapper productModelMapper;
    @Autowired
    private ProductMapper productMapper;
    @Autowired
    private StockUninventoryService stockUninventoryService;
    @Override
    public IPage<StockInventoryDto> pagestockInventory(Page page, StockInventoryDto stockInventoryDto) {
        return stockInventoryMapper.pagestockInventory(page, stockInventoryDto);
    }
    //入库调用
    @Override
    public List<FinishedProductTreeDto> finishedProductList(StockInventoryDto stockInventoryDto) {
        // 查询库存数据
        List<FinishedProductTreeDto> dataList = stockInventoryMapper.selectFinishedProductList(stockInventoryDto);
        if (dataList.isEmpty()) {
            return new ArrayList<>();
        }
        // 按产品ID分组,构建树形结构
        Map<Long, FinishedProductTreeDto> productMap = new LinkedHashMap<>();
        for (FinishedProductTreeDto data : dataList) {
            Long productId = data.getProductId();
            if (!productMap.containsKey(productId)) {
                // 创建产品大类节点
                FinishedProductTreeDto productNode = new FinishedProductTreeDto();
                productNode.setProductId(productId);
                productNode.setProductName(data.getProductName());
                productNode.setLabel(data.getLabel());
                productNode.setChildren(new ArrayList<>());
                productMap.put(productId, productNode);
            }
            // 添加库存叶子节点
            FinishedProductTreeDto leafNode = new FinishedProductTreeDto();
            leafNode.setStockId(data.getStockId());
            leafNode.setProductModelId(data.getProductModelId());
            leafNode.setModel(data.getModel());
            leafNode.setProcessCategory(data.getProcessCategory());
            leafNode.setVoltage(data.getVoltage());
            leafNode.setMaterialCode(data.getMaterialCode());
            leafNode.setUnit(data.getUnit());
            leafNode.setQualitity(data.getQualitity());
            productMap.get(productId).getChildren().add(leafNode);
        }
        return new ArrayList<>(productMap.values());
    }
    @Override
    public IPage<StockInventoryDto> pageListCombinedStockInventory(Page page, StockInventoryDto stockInventoryDto) {
        return stockInventoryMapper.pageListCombinedStockInventory(page, stockInventoryDto);
    }
    /**
     * 合格入库:先生成入库记录,再走审批流。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addstockInventory(StockInventoryDto stockInventoryDto) {
        //新增入库记录再添加库存
        List<StockInventory> stockInventoryList = stockInventoryMapper.selectList(null);
        StockInRecordDto stockInRecordDto = new StockInRecordDto();
        stockInRecordDto.setRecordId(stockInventoryDto.getRecordId());
        stockInRecordDto.setRecordType(stockInventoryDto.getRecordType());
        stockInRecordDto.setStockInNum(stockInventoryDto.getQualitity());
        stockInRecordDto.setProductModelId(stockInventoryDto.getProductModelId());
        stockInRecordService.add(stockInRecordDto);
        //再进行新增库存数量库存
        //先查询库存表中的产品是否存在,不存在新增,存在更新
        StockInventory oldStockInventory = stockInventoryMapper.selectOne(new QueryWrapper<StockInventory>().lambda().eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId()));
        if (ObjectUtils.isEmpty(oldStockInventory)) {
            StockInventory newStockInventory = new StockInventory();
            newStockInventory.setProductModelId(stockInventoryDto.getProductModelId());
            newStockInventory.setQualitity(stockInventoryDto.getQualitity());
            newStockInventory.setVersion(1);
            stockInventoryMapper.insert(newStockInventory);
        }else {
             stockInventoryMapper.updateAddStockInventory(stockInventoryDto);
        stockInRecordDto.setRemark(stockInventoryDto.getRemark());
        stockInRecordDto.setWarnNum(stockInventoryDto.getWarnNum());
        stockInRecordDto.setLockedQuantity(stockInventoryDto.getLockedQuantity());
        stockInRecordDto.setProcessCategory(normalizeDimension(stockInventoryDto.getProcessCategory()));
        stockInRecordDto.setVoltage(normalizeDimension(stockInventoryDto.getVoltage()));
        stockInRecordDto.setApproveStatus(0);
        stockInRecordDto.setType("0");
        if (StringUtils.isBlank(stockInventoryDto.getBatchNo())) {
            String batchNo;
            LocalDate now = LocalDate.now();
            String monthFlag = now.format(DateTimeFormatter.ofPattern("MM"));
            int maxSeq = getCurrentMonthMaxSeq(stockInventoryDto, monthFlag, stockInventoryList);
            int newSeq = maxSeq + 1;
            String seqStr = String.format("%03d", newSeq);
            ProductModel productModel = productModelMapper.selectById(stockInventoryDto.getProductModelId());
            batchNo = stockInventoryDto.getMaterialCode() + productModel.getModel() + "P" + monthFlag + seqStr;
            stockInRecordDto.setBatchNo(batchNo);
        } else {
            stockInRecordDto.setBatchNo(stockInventoryDto.getBatchNo());
        }
        Long id = stockInRecordService.add(stockInRecordDto);
        LoginUser loginUser = SecurityUtils.getLoginUser();
        if (id != null) {
            try {
                addApproveByPurchase(loginUser, stockInRecordDto, id);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage());
            }
        }
        return true;
    }
    //出库调用
    private static int getCurrentMonthMaxSeq(StockInventoryDto dto, String monthFlag, List<StockInventory> existingList) {
        int maxSeq = 0;
        String prefix = dto.getMaterialCode() + dto.getProductModelName() + "P" + monthFlag;
        Pattern pattern = Pattern.compile(Pattern.quote(prefix) + "(\\d{3})");
        for (StockInventory item : existingList) {
            String batchNo = item.getBatchNo();
            if (batchNo == null) {
                continue;
            }
            Matcher matcher = pattern.matcher(batchNo);
            if (matcher.find()) {
                int seq = Integer.parseInt(matcher.group(1));
                if (seq > maxSeq) {
                    maxSeq = seq;
                }
            }
        }
        return maxSeq;
    }
    @Override
    public void addApproveByPurchase(LoginUser loginUser, StockInRecordDto stockInRecordDto, Long id) throws Exception {
        ApproveProcessVO approveProcessVO = new ApproveProcessVO();
        approveProcessVO.setApproveType(9);
        approveProcessVO.setApproveDeptId(loginUser.getCurrentDeptId());
        approveProcessVO.setApproveReason(stockInRecordDto.getInboundBatches());
        approveProcessVO.setApproveUserIds(String.valueOf(1));
        approveProcessVO.setApproveUser(loginUser.getUserId());
        approveProcessVO.setApproveTime(LocalDate.now().toString());
        approveProcessVO.setInventoryReview(false);
        approveProcessVO.setStorageType("合格入库");
        approveProcessVO.setRecordId(id);
        approveProcessService.addApprove(approveProcessVO);
    }
    /**
     * 按库存唯一键合并库存。
     * 成品使用 product_model_id + processCategory + voltage;其他入库只按 product_model_id。
     */
    @Override
    public void updateOrCreateStockInventory(StockInRecord stockInRecord) {
        String processCategory = normalizeDimension(stockInRecord.getProcessCategory());
        String voltage = normalizeDimension(stockInRecord.getVoltage());
        StockInventory oldStockInventory = findInventoryForMerge(
                stockInRecord.getProductModelId(),
                processCategory,
                voltage
        );
        if (ObjectUtils.isEmpty(oldStockInventory)) {
            StockInventory newStockInventory = new StockInventory();
            newStockInventory.setProductModelId(stockInRecord.getProductModelId());
            newStockInventory.setQualitity(defaultDecimal(stockInRecord.getStockInNum()));
            newStockInventory.setVersion(1);
            newStockInventory.setRemark(stockInRecord.getRemark());
            newStockInventory.setLockedQuantity(defaultDecimal(stockInRecord.getLockedQuantity()));
            newStockInventory.setWarnNum(stockInRecord.getWarnNum());
            newStockInventory.setProcessCategory(processCategory);
            newStockInventory.setVoltage(voltage);
            newStockInventory.setBatchNo(StringUtils.trimToEmpty(stockInRecord.getBatchNo()));
            stockInventoryMapper.insert(newStockInventory);
            return;
        }
        oldStockInventory.setQualitity(defaultDecimal(oldStockInventory.getQualitity()).add(defaultDecimal(stockInRecord.getStockInNum())));
        oldStockInventory.setVersion(oldStockInventory.getVersion() == null ? 1 : oldStockInventory.getVersion() + 1);
        if (StringUtils.isNotBlank(stockInRecord.getRemark())) {
            oldStockInventory.setRemark(stockInRecord.getRemark());
        }
        if (stockInRecord.getWarnNum() != null) {
            oldStockInventory.setWarnNum(stockInRecord.getWarnNum());
        }
        if (stockInRecord.getLockedQuantity() != null) {
            oldStockInventory.setLockedQuantity(defaultDecimal(oldStockInventory.getLockedQuantity()).add(stockInRecord.getLockedQuantity()));
        }
        oldStockInventory.setProcessCategory(processCategory);
        oldStockInventory.setVoltage(voltage);
        oldStockInventory.setUpdateTime(LocalDateTime.now());
        stockInventoryMapper.updateById(oldStockInventory);
    }
    /**
     * 不审核入库,直接写入入库记录和库存。
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean addstockInventoryNoReview(StockInventoryDto stockInventoryDto) {
        StockInRecordDto stockInRecordDto = new StockInRecordDto();
        stockInRecordDto.setRecordId(stockInventoryDto.getRecordId());
        stockInRecordDto.setRecordType(stockInventoryDto.getRecordType());
        stockInRecordDto.setStockInNum(stockInventoryDto.getQualitity());
        stockInRecordDto.setProductModelId(stockInventoryDto.getProductModelId());
        stockInRecordDto.setRemark(stockInventoryDto.getRemark());
        stockInRecordDto.setWarnNum(stockInventoryDto.getWarnNum());
        stockInRecordDto.setLockedQuantity(stockInventoryDto.getLockedQuantity());
        stockInRecordDto.setProcessCategory(normalizeDimension(stockInventoryDto.getProcessCategory()));
        stockInRecordDto.setVoltage(normalizeDimension(stockInventoryDto.getVoltage()));
        stockInRecordDto.setBatchNo(stockInventoryDto.getBatchNo());
        stockInRecordDto.setType("0");
        stockInRecordService.add(stockInRecordDto);
        updateOrCreateStockInventory(toStockInRecord(stockInRecordDto));
        return true;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean subtractStockInventory(StockInventoryDto stockInventoryDto) {
            //  新增出库记录
        if (stockInventoryDto.getQualitity() != null
                && stockInventoryDto.getQualitity().compareTo(BigDecimal.ZERO) == 0) {
            return true;
        }
        StockOutRecordDto stockOutRecordDto = new StockOutRecordDto();
        stockOutRecordDto.setRecordId(stockInventoryDto.getRecordId());
        stockOutRecordDto.setRecordType(stockInventoryDto.getRecordType());
        stockOutRecordDto.setStockOutNum(stockInventoryDto.getQualitity());
        stockOutRecordDto.setProductModelId(stockInventoryDto.getProductModelId());
        stockOutRecordDto.setType("0");
        stockOutRecordService.add(stockOutRecordDto);
        StockInventory oldStockInventory = stockInventoryMapper.selectOne(new QueryWrapper<StockInventory>().lambda().eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId()));
        //销售出库按照保存的库存id
        if (stockInventoryDto.getStockId() != null) {
            StockInventory stockInventory = stockInventoryMapper.selectById(stockInventoryDto.getStockId());
            if (ObjectUtils.isEmpty(stockInventory)) {
                throw new RuntimeException("产品库存不存在");
            }
            BigDecimal lockedQty = defaultDecimal(stockInventory.getLockedQuantity());
            if (stockInventoryDto.getQualitity().compareTo(defaultDecimal(stockInventory.getQualitity()).subtract(lockedQty)) > 0) {
                ProductModel productModel = productModelMapper.selectById(stockInventoryDto.getProductModelId());
                Product product = productMapper.selectById(productModel.getProductId());
                throw new RuntimeException(product.getProductName() + "/" + productModel.getModel() + "库存不足,无法出库");
            }
            stockInventory.setQualitity(defaultDecimal(stockInventory.getQualitity()).subtract(stockInventoryDto.getQualitity()));
            stockInventory.setVersion(stockInventory.getVersion() == null ? 1 : stockInventory.getVersion() + 1);
            stockInventory.setUpdateTime(LocalDateTime.now());
            stockInventoryMapper.updateById(stockInventory);
            return true;
        }
        if (StringUtils.isBlank(stockInventoryDto.getBatchNo()) && !usesDimensionIdentity(normalizeDimension(stockInventoryDto.getProcessCategory()), normalizeDimension(stockInventoryDto.getVoltage()))) {
            List<StockInventory> stockInventories = stockInventoryMapper.selectList(new QueryWrapper<StockInventory>().lambda()
                    .eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId())
                    .orderByAsc(StockInventory::getId));
            if (ObjectUtils.isEmpty(stockInventories)) {
                throw new RuntimeException("产品库存不存在");
            }
            BigDecimal remainingQty = stockInventoryDto.getQualitity() == null ? BigDecimal.ZERO : stockInventoryDto.getQualitity();
            for (StockInventory stockInventory : stockInventories) {
                BigDecimal lockedQty = defaultDecimal(stockInventory.getLockedQuantity());
                BigDecimal availableQty = defaultDecimal(stockInventory.getQualitity()).subtract(lockedQty);
                if (availableQty.compareTo(BigDecimal.ZERO) <= 0) {
                    continue;
                }
                BigDecimal deductQty = remainingQty.min(availableQty);
                stockInventory.setQualitity(defaultDecimal(stockInventory.getQualitity()).subtract(deductQty));
                stockInventory.setVersion(stockInventory.getVersion() == null ? 1 : stockInventory.getVersion() + 1);
                stockInventory.setUpdateTime(LocalDateTime.now());
                stockInventoryMapper.updateById(stockInventory);
                remainingQty = remainingQty.subtract(deductQty);
                if (remainingQty.compareTo(BigDecimal.ZERO) <= 0) {
                    return true;
                }
            }
            ProductModel productModel = productModelMapper.selectById(stockInventoryDto.getProductModelId());
            Product product = productMapper.selectById(productModel.getProductId());
            throw new RuntimeException(product.getProductName() + "/" + productModel.getModel() + "库存不足,无法出库");
        }
        StockInventory oldStockInventory = findInventoryForMerge(
                stockInventoryDto.getProductModelId(),
                stockInventoryDto.getProcessCategory(),
                stockInventoryDto.getVoltage()
        );
        if (ObjectUtils.isEmpty(oldStockInventory)) {
            throw new RuntimeException("产品库存不存在");
        }else {
            stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto);
        }
        BigDecimal lockedQty = defaultDecimal(oldStockInventory.getLockedQuantity());
        if (stockInventoryDto.getQualitity().compareTo(defaultDecimal(oldStockInventory.getQualitity()).subtract(lockedQty)) > 0) {
            ProductModel productModel = productModelMapper.selectById(stockInventoryDto.getProductModelId());
            Product product = productMapper.selectById(productModel.getProductId());
            throw new RuntimeException(product.getProductName() + "/" + productModel.getModel() + "库存不足,无法出库");
        }
        oldStockInventory.setQualitity(defaultDecimal(oldStockInventory.getQualitity()).subtract(stockInventoryDto.getQualitity()));
        oldStockInventory.setVersion(oldStockInventory.getVersion() == null ? 1 : oldStockInventory.getVersion() + 1);
        oldStockInventory.setUpdateTime(LocalDateTime.now());
        stockInventoryMapper.updateById(oldStockInventory);
        return true;
    }
    @Override
    public R importStockInventory(MultipartFile file, Integer productType) {
        try {
            List<SalesLedgerProduct> salesLedgerProducts = salesLedgerProductMapper.selectProduct();
            Map<String, SalesLedgerProduct> productMap = new HashMap<>();
            for (SalesLedgerProduct product : salesLedgerProducts) {
                String key = product.getProductCategory() + "|" + product.getSpecificationModel();
                productMap.put(key, product);
            }
            List<String> unmatchedRecords = new ArrayList<>();
            int successCount = 0;
            // productType: 1=成品, 0=非成品
            if (productType == 1) {
                // 成品导入(包含工序类别和电压)
                ExcelUtil<FinishedProductInventoryExportData> util = new ExcelUtil<>(FinishedProductInventoryExportData.class);
                List<FinishedProductInventoryExportData> list = util.importExcel(file.getInputStream());
                for (FinishedProductInventoryExportData dto : list) {
                    String key = dto.getProductName() + "|" + dto.getModel();
                    SalesLedgerProduct matchedProduct = productMap.get(key);
                    if (matchedProduct != null) {
                        if (dto.getQualifiedQuantity() != null && dto.getQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
                            StockInventoryDto stockInventoryDto = new StockInventoryDto();
                            stockInventoryDto.setRecordId(0L);
                            stockInventoryDto.setRecordType(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode());
                            stockInventoryDto.setQualitity(dto.getQualifiedQuantity());
                            stockInventoryDto.setRemark(dto.getRemark());
                            stockInventoryDto.setWarnNum(dto.getWarnNum());
                            stockInventoryDto.setBatchNo(dto.getQualifiedBatchNo());
                            stockInventoryDto.setProcessCategory(dto.getProcessCategory());
                            stockInventoryDto.setVoltage(dto.getVoltage());
                            if (ObjectUtils.isNotEmpty(dto.getQualifiedLockedQuantity())) {
                                if (dto.getQualifiedLockedQuantity().compareTo(dto.getQualifiedQuantity()) > 0) {
                                    throw new RuntimeException("合格冻结数量不能超过本次导入的合格库存数量");
                                }
                                stockInventoryDto.setLockedQuantity(dto.getQualifiedLockedQuantity());
                            } else {
                                stockInventoryDto.setLockedQuantity(BigDecimal.ZERO);
                            }
                            stockInventoryDto.setProductModelId(matchedProduct.getProductModelId());
                            this.addstockInventory(stockInventoryDto);
                            successCount++;
                        }
                        if (dto.getUnQualifiedQuantity() != null && dto.getUnQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
                            StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
                            stockUninventoryDto.setRecordId(0L);
                            stockUninventoryDto.setRecordType(StockInUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode());
                            stockUninventoryDto.setQualitity(dto.getUnQualifiedQuantity());
                            stockUninventoryDto.setRemark(dto.getRemark());
                            stockUninventoryDto.setBatchNo(dto.getUnQualifiedBatchNo());
                            if (ObjectUtils.isNotEmpty(dto.getUnQualifiedLockedQuantity())) {
                                if (dto.getUnQualifiedLockedQuantity().compareTo(dto.getUnQualifiedQuantity()) > 0) {
                                    throw new RuntimeException("不合格冻结数量不能超过本次导入的不合格库存数量");
                                }
                                stockUninventoryDto.setLockedQuantity(dto.getUnQualifiedLockedQuantity());
                            } else {
                                stockUninventoryDto.setLockedQuantity(BigDecimal.ZERO);
                            }
                            stockUninventoryDto.setProductModelId(matchedProduct.getProductModelId());
                            stockUninventoryService.addStockUninventory(stockUninventoryDto);
                            successCount++;
                        }
                    } else {
                        String unmatchedRecord = "产品名称:" + dto.getProductName() + ",型号:" + dto.getModel() + " 未匹配到库存产品";
                        unmatchedRecords.add(unmatchedRecord);
                    }
                }
            } else {
                // 非成品导入(不包含工序类别和电压)
                ExcelUtil<NonFinishedProductInventoryExportData> util = new ExcelUtil<>(NonFinishedProductInventoryExportData.class);
                List<NonFinishedProductInventoryExportData> list = util.importExcel(file.getInputStream());
                for (NonFinishedProductInventoryExportData dto : list) {
                    String key = dto.getProductName() + "|" + dto.getModel();
                    SalesLedgerProduct matchedProduct = productMap.get(key);
                    if (matchedProduct != null) {
                        if (dto.getQualifiedQuantity() != null && dto.getQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
                            StockInventoryDto stockInventoryDto = new StockInventoryDto();
                            stockInventoryDto.setRecordId(0L);
                            stockInventoryDto.setRecordType(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode());
                            stockInventoryDto.setQualitity(dto.getQualifiedQuantity());
                            stockInventoryDto.setRemark(dto.getRemark());
                            stockInventoryDto.setWarnNum(dto.getWarnNum());
                            stockInventoryDto.setBatchNo(dto.getQualifiedBatchNo());
                            if (ObjectUtils.isNotEmpty(dto.getQualifiedLockedQuantity())) {
                                if (dto.getQualifiedLockedQuantity().compareTo(dto.getQualifiedQuantity()) > 0) {
                                    throw new RuntimeException("合格冻结数量不能超过本次导入的合格库存数量");
                                }
                                stockInventoryDto.setLockedQuantity(dto.getQualifiedLockedQuantity());
                            } else {
                                stockInventoryDto.setLockedQuantity(BigDecimal.ZERO);
                            }
                            stockInventoryDto.setProductModelId(matchedProduct.getProductModelId());
                            this.addstockInventory(stockInventoryDto);
                            successCount++;
                        }
                        if (dto.getUnQualifiedQuantity() != null && dto.getUnQualifiedQuantity().compareTo(BigDecimal.ZERO) > 0) {
                            StockUninventoryDto stockUninventoryDto = new StockUninventoryDto();
                            stockUninventoryDto.setRecordId(0L);
                            stockUninventoryDto.setRecordType(StockInUnQualifiedRecordTypeEnum.CUSTOMIZATION_UNSTOCK_IN.getCode());
                            stockUninventoryDto.setQualitity(dto.getUnQualifiedQuantity());
                            stockUninventoryDto.setRemark(dto.getRemark());
                            stockUninventoryDto.setBatchNo(dto.getUnQualifiedBatchNo());
                            if (ObjectUtils.isNotEmpty(dto.getUnQualifiedLockedQuantity())) {
                                if (dto.getUnQualifiedLockedQuantity().compareTo(dto.getUnQualifiedQuantity()) > 0) {
                                    throw new RuntimeException("不合格冻结数量不能超过本次导入的不合格库存数量");
                                }
                                stockUninventoryDto.setLockedQuantity(dto.getUnQualifiedLockedQuantity());
                            } else {
                                stockUninventoryDto.setLockedQuantity(BigDecimal.ZERO);
                            }
                            stockUninventoryDto.setProductModelId(matchedProduct.getProductModelId());
                            stockUninventoryService.addStockUninventory(stockUninventoryDto);
                            successCount++;
                        }
                    } else {
                        String unmatchedRecord = "产品名称:" + dto.getProductName() + ",型号:" + dto.getModel() + " 未匹配到库存产品";
                        unmatchedRecords.add(unmatchedRecord);
                    }
                }
            }
            StringBuilder message = new StringBuilder();
            if (!unmatchedRecords.isEmpty()) {
                message.append("导入成功 ").append(successCount).append(" 条记录,以下产品未匹配:\n");
                for (String record : unmatchedRecords) {
                    message.append(record).append("\n");
                }
                return R.ok(message.toString());
            }
            return R.ok("导入成功,共处理 " + successCount + " 条记录");
        } catch (Exception e) {
            log.error("库存导入失败", e);
            return R.fail("库存导入失败:" + e.getMessage());
        }
    }
    @Override
    public void exportStockInventory(HttpServletResponse response, StockInventoryDto stockInventoryDto, Integer productType) {
        List<StockInventoryExportData> list = stockInventoryMapper.listStockInventoryExportData(stockInventoryDto);
        // productType: 1=成品, 0=非成品
        if (productType == 1) {
            // 成品导出(包含工序类别和电压)
            List<FinishedProductInventoryExportData> finishedList = new ArrayList<>();
            for (StockInventoryExportData data : list) {
                FinishedProductInventoryExportData finishedData = new FinishedProductInventoryExportData();
                finishedData.setProductName(data.getProductName());
                finishedData.setModel(data.getModel());
                finishedData.setUnit(data.getUnit());
                finishedData.setMaterialCode(data.getMaterialCode());
                finishedData.setProcessCategory(data.getProcessCategory());
                finishedData.setVoltage(data.getVoltage());
                finishedData.setQualifiedBatchNo(data.getQualifiedBatchNo());
                finishedData.setUnQualifiedBatchNo(data.getUnQualifiedBatchNo());
                finishedData.setQualifiedQuantity(data.getQualifiedQuantity());
                finishedData.setUnQualifiedQuantity(data.getUnQualifiedQuantity());
                finishedData.setWarnNum(data.getWarnNum());
                finishedData.setQualifiedLockedQuantity(data.getQualifiedLockedQuantity());
                finishedData.setUnQualifiedLockedQuantity(data.getUnQualifiedLockedQuantity());
                finishedData.setRemark(data.getRemark());
                finishedList.add(finishedData);
            }
            ExcelUtil<FinishedProductInventoryExportData> util = new ExcelUtil<>(FinishedProductInventoryExportData.class);
            util.exportExcel(response, finishedList, "成品库存信息");
        } else {
            // 非成品导出(不包含工序类别和电压)
            List<NonFinishedProductInventoryExportData> nonFinishedList = new ArrayList<>();
            for (StockInventoryExportData data : list) {
                NonFinishedProductInventoryExportData nonFinishedData = new NonFinishedProductInventoryExportData();
                nonFinishedData.setProductName(data.getProductName());
                nonFinishedData.setModel(data.getModel());
                nonFinishedData.setUnit(data.getUnit());
                nonFinishedData.setMaterialCode(data.getMaterialCode());
                nonFinishedData.setQualifiedBatchNo(data.getQualifiedBatchNo());
                nonFinishedData.setUnQualifiedBatchNo(data.getUnQualifiedBatchNo());
                nonFinishedData.setQualifiedQuantity(data.getQualifiedQuantity());
                nonFinishedData.setUnQualifiedQuantity(data.getUnQualifiedQuantity());
                nonFinishedData.setWarnNum(data.getWarnNum());
                nonFinishedData.setQualifiedLockedQuantity(data.getQualifiedLockedQuantity());
                nonFinishedData.setUnQualifiedLockedQuantity(data.getUnQualifiedLockedQuantity());
                nonFinishedData.setRemark(data.getRemark());
                nonFinishedList.add(nonFinishedData);
            }
            ExcelUtil<NonFinishedProductInventoryExportData> util = new ExcelUtil<>(NonFinishedProductInventoryExportData.class);
            util.exportExcel(response, nonFinishedList, "非成品库存信息");
        }
    }
    @Override
    public IPage<StockInRecordDto> stockInventoryPage(StockInventoryDto stockInventoryDto, Page page) {
        return stockInventoryMapper.stockInventoryPage(stockInventoryDto, page);
    }
    @Override
    public IPage<StockInventoryDto> stockInAndOutRecord(StockInventoryDto stockInventoryDto, Page page) {
        return stockInventoryMapper.stockInAndOutRecord(stockInventoryDto, page);
    }
    @Override
    public Boolean frozenStock(StockInventoryDto stockInventoryDto) {
        StockInventory stockInventory = stockInventoryMapper.selectById(stockInventoryDto.getId());
        if (stockInventory.getQualitity().compareTo(stockInventoryDto.getLockedQuantity()) < 0) {
            throw new RuntimeException("冻结数量不能超过库存数量");
        }
        if (ObjectUtils.isEmpty(stockInventory.getLockedQuantity())) {
            stockInventory.setLockedQuantity(stockInventoryDto.getLockedQuantity());
        } else {
            stockInventory.setLockedQuantity(stockInventory.getLockedQuantity().add(stockInventoryDto.getLockedQuantity()));
        }
        return this.updateById(stockInventory);
    }
    @Override
    public Boolean thawStock(StockInventoryDto stockInventoryDto) {
        StockInventory stockInventory = stockInventoryMapper.selectById(stockInventoryDto.getId());
        if (stockInventory.getLockedQuantity().compareTo(stockInventoryDto.getLockedQuantity()) < 0) {
            throw new RuntimeException("解冻数量不能超过冻结数量");
        }
        stockInventory.setLockedQuantity(stockInventory.getLockedQuantity().subtract(stockInventoryDto.getLockedQuantity()));
        return this.updateById(stockInventory);
    }
    // 入库合并唯一键:成品按类别和电压,其余只按产品规格。
    private StockInventory findInventoryForMerge(Long productModelId, String processCategory, String voltage) {
        LambdaQueryWrapper<StockInventory> queryWrapper = new LambdaQueryWrapper<StockInventory>()
                .eq(StockInventory::getProductModelId, productModelId)
                .orderByAsc(StockInventory::getId);
        if (usesDimensionIdentity(processCategory, voltage)) {
            queryWrapper.eq(StockInventory::getProcessCategory, normalizeDimension(processCategory));
            queryWrapper.eq(StockInventory::getVoltage, normalizeDimension(voltage));
        }
        List<StockInventory> inventories = stockInventoryMapper.selectList(queryWrapper);
        return inventories.isEmpty() ? null : inventories.get(0);
    }
    // 将入库记录 DTO 复制为持久化实体。
    private StockInRecord toStockInRecord(StockInRecordDto stockInRecordDto) {
        StockInRecord stockInRecord = new StockInRecord();
        stockInRecord.setRecordId(stockInRecordDto.getRecordId());
        stockInRecord.setRecordType(stockInRecordDto.getRecordType());
        stockInRecord.setStockInNum(stockInRecordDto.getStockInNum());
        stockInRecord.setProductModelId(stockInRecordDto.getProductModelId());
        stockInRecord.setRemark(stockInRecordDto.getRemark());
        stockInRecord.setType(stockInRecordDto.getType());
        stockInRecord.setLockedQuantity(stockInRecordDto.getLockedQuantity());
        stockInRecord.setWarnNum(stockInRecordDto.getWarnNum());
        stockInRecord.setApproveStatus(stockInRecordDto.getApproveStatus());
        stockInRecord.setBatchNo(stockInRecordDto.getBatchNo());
        stockInRecord.setProcessCategory(stockInRecordDto.getProcessCategory());
        stockInRecord.setVoltage(stockInRecordDto.getVoltage());
        return stockInRecord;
    }
    // 只要带有成品维度,就按维度作为唯一键。
    private boolean usesDimensionIdentity(String processCategory, String voltage) {
        return StringUtils.isNotBlank(processCategory) || StringUtils.isNotBlank(voltage);
    }
    private String normalizeDimension(String value) {
        return StringUtils.trimToEmpty(value);
    }
    private BigDecimal defaultDecimal(BigDecimal value) {
        return value == null ? BigDecimal.ZERO : value;
    }
}