liding
6 天以前 4858d6a68446a5153749eca8fae8ae099ac879a2
src/main/java/com/ruoyi/stock/service/impl/StockInventoryServiceImpl.java
@@ -1,7 +1,7 @@
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.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@@ -13,15 +13,17 @@
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.sales.mapper.SalesLedgerProductMapper;
import com.ruoyi.sales.pojo.SalesLedgerProduct;
import com.ruoyi.stock.dto.StockInRecordDto;
import com.ruoyi.stock.dto.StockInventoryDto;
import com.ruoyi.stock.dto.StockOutRecordDto;
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;
@@ -29,6 +31,7 @@
import com.ruoyi.stock.service.StockInRecordService;
import com.ruoyi.stock.service.StockInventoryService;
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;
@@ -37,21 +40,12 @@
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.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * <p>
 * 库存表 服务实现类
 * </p>
 *
 * @author 芯导软件(江苏)有限公司
 * @since 2026-01-21 04:16:36
 */
@Service
public class StockInventoryServiceImpl extends ServiceImpl<StockInventoryMapper, StockInventory> implements StockInventoryService {
@@ -69,18 +63,63 @@
    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());
@@ -89,23 +128,19 @@
        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 (stockInventoryDto.getBatchNo() == null || stockInventoryDto.getBatchNo().isEmpty()) {
        if (StringUtils.isBlank(stockInventoryDto.getBatchNo())) {
            String batchNo;
            // 获取当前月份(两位)
            LocalDate now = LocalDate.now();
            String monthFlag = now.format(DateTimeFormatter.ofPattern("MM"));
            // 获取当前月份的最大流水号
            int maxSeq = getCurrentMonthMaxSeq(stockInventoryDto, monthFlag, stockInventoryList);
            // 新流水号 = 最大流水号 + 1
            int newSeq = maxSeq + 1;
            String seqStr = String.format("%03d", newSeq);
            // 组装batchNo
            batchNo = stockInventoryDto.getMaterialCode() + stockInventoryDto.getModel() + "P" + monthFlag + seqStr;
            ProductModel productModel = productModelMapper.selectById(stockInventoryDto.getProductModelId());
            batchNo = stockInventoryDto.getMaterialCode() + productModel.getModel() + "P" + monthFlag + seqStr;
            stockInRecordDto.setBatchNo(batchNo);
        } else {
            stockInRecordDto.setBatchNo(stockInventoryDto.getBatchNo());
@@ -122,21 +157,15 @@
        return true;
    }
    /**
     * 查询当前月份已存在的最大流水号
     */
    private static int getCurrentMonthMaxSeq(StockInventoryDto dto, String monthFlag, List<StockInventory> existingList) {
        int maxSeq = 0;
        String prefix = dto.getMaterialCode() + dto.getModel() + "P" + monthFlag;
        // 正则匹配:前缀 + 3位数字
        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;
            if (batchNo == null) {
                continue;
            }
            Matcher matcher = pattern.matcher(batchNo);
            if (matcher.find()) {
                int seq = Integer.parseInt(matcher.group(1));
@@ -145,10 +174,10 @@
                }
            }
        }
        return maxSeq;
    }
    @Override
    public void addApproveByPurchase(LoginUser loginUser, StockInRecordDto stockInRecordDto, Long id) throws Exception {
        ApproveProcessVO approveProcessVO = new ApproveProcessVO();
        approveProcessVO.setApproveType(9);
@@ -164,85 +193,82 @@
    }
    /**
     * 实际入库
     *
     * @param stockInRecord
     * 按库存唯一键合并库存。
     * 成品使用 product_model_id + processCategory + voltage;其他入库只按 product_model_id。
     */
    @Override
    public void updateOrCreateStockInventory(StockInRecord stockInRecord) {
        // 先查询库存表中的产品是否存在
        StockInventory oldStockInventory = stockInventoryMapper.selectOne(
                new QueryWrapper<StockInventory>().lambda()
                        .eq(StockInventory::getProductModelId, stockInRecord.getProductModelId())
                        .eq(StockInventory::getBatchNo, stockInRecord.getBatchNo())
        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(stockInRecord.getStockInNum());
            newStockInventory.setQualitity(defaultDecimal(stockInRecord.getStockInNum()));
            newStockInventory.setVersion(1);
            newStockInventory.setRemark(stockInRecord.getRemark());
            newStockInventory.setLockedQuantity(stockInRecord.getLockedQuantity());
            newStockInventory.setLockedQuantity(defaultDecimal(stockInRecord.getLockedQuantity()));
            newStockInventory.setWarnNum(stockInRecord.getWarnNum());
            newStockInventory.setBatchNo(stockInRecord.getBatchNo());
            newStockInventory.setProcessCategory(processCategory);
            newStockInventory.setVoltage(voltage);
            newStockInventory.setBatchNo(StringUtils.trimToEmpty(stockInRecord.getBatchNo()));
            stockInventoryMapper.insert(newStockInventory);
        } else {
            // 存在则更新
            LambdaUpdateWrapper<StockInventory> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper
                    .eq(StockInventory::getProductModelId, stockInRecord.getProductModelId())
                    .eq(StockInventory::getBatchNo, stockInRecord.getBatchNo())
                    .setSql(stockInRecord.getStockInNum() != null,
                            "qualitity = qualitity + " + stockInRecord.getStockInNum())
                    .setSql(true, "version = version + 1")
                    .set(stockInRecord.getRemark() != null && !stockInRecord.getRemark().isEmpty(),
                            StockInventory::getRemark, stockInRecord.getRemark())
                    .set(stockInRecord.getWarnNum() != null,
                            StockInventory::getWarnNum, stockInRecord.getWarnNum())
                    .setSql(stockInRecord.getLockedQuantity() != null,
                            "locked_quantity = locked_quantity + " + stockInRecord.getLockedQuantity())
                    .set(StockInventory::getUpdateTime, new Date());
            stockInventoryMapper.update(null, updateWrapper);
            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);
        //再进行新增库存数量库存
        //先查询库存表中的产品是否存在,不存在新增,存在更新
        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);
            newStockInventory.setRemark(stockInventoryDto.getRemark());
            newStockInventory.setLockedQuantity(stockInventoryDto.getLockedQuantity());
            newStockInventory.setWarnNum(stockInventoryDto.getWarnNum());
            stockInventoryMapper.insert(newStockInventory);
        } else {
            stockInventoryMapper.updateAddStockInventory(stockInventoryDto);
        }
        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());
@@ -250,88 +276,283 @@
        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("产品库存不存在");
        }
        BigDecimal lockedQty = oldStockInventory.getLockedQuantity();
        if (lockedQty == null) {
            lockedQty = BigDecimal.ZERO;
        }
        if (stockInventoryDto.getQualitity().compareTo(oldStockInventory.getQualitity().subtract(lockedQty)) > 0) {
            // 查询产品规格名
        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() + "库存不足无法出库");
            throw new RuntimeException(product.getProductName() + "/" + productModel.getModel() + "库存不足,无法出库");
        }
        stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto);
        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) {
    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);
            }
            ExcelUtil<StockInventoryExportData> util = new ExcelUtil<StockInventoryExportData>(StockInventoryExportData.class);
            List<StockInventoryExportData> list = util.importExcel(file.getInputStream());
            // 记录未找到匹配项的数据
            List<String> unmatchedRecords = new ArrayList<>();
            int successCount = 0;
            list.forEach(dto -> {
                boolean matched = false;
                for (SalesLedgerProduct item : salesLedgerProducts) {
                    if (item.getProductCategory().equals(dto.getProductName()) &&
                            item.getSpecificationModel().equals(dto.getModel())) {
                        StockInventoryDto stockInventoryDto = new StockInventoryDto();
                        stockInventoryDto.setRecordId(0L);
                        stockInventoryDto.setRecordType(StockInQualifiedRecordTypeEnum.CUSTOMIZATION_STOCK_IN.getCode());
                        stockInventoryDto.setQualitity(dto.getQualitity());
                        stockInventoryDto.setRemark(dto.getRemark());
                        stockInventoryDto.setWarnNum(dto.getWarnNum());
                        if (ObjectUtils.isNotEmpty(dto.getLockedQuantity()) && dto.getLockedQuantity().compareTo(dto.getQualitity()) > 0) {
                            throw new RuntimeException("冻结数量不能超过本次导入的库存数量");
            // 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++;
                        }
                        stockInventoryDto.setLockedQuantity(dto.getLockedQuantity());
                        stockInventoryDto.setProductModelId(item.getProductModelId());
                        this.addstockInventory(stockInventoryDto);
                        matched = true;
                        break; // 找到匹配项后跳出循环
                        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);
                    }
                }
                if (!matched) {
                    // 记录未匹配的数据
                    String unmatchedInfo = String.format("产品名称:%s,规格型号:%s",
                            dto.getProductName(), dto.getModel());
                    unmatchedRecords.add(unmatchedInfo);
            } 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("以下产品未找到匹配项:\n");
                message.append("导入成功 ").append(successCount).append(" 条记录,以下产品未匹配:\n");
                for (String record : unmatchedRecords) {
                    message.append(record).append("\n");
                }
                throw new RuntimeException(message.toString());
                return R.ok(message.toString());
            }
            return R.ok("导入成功,共处理 " + successCount + " 条记录");
        } catch (Exception e) {
            e.printStackTrace();
            return R.fail("导入失败:" + e.getMessage());
            log.error("库存导入失败", e);
            return R.fail("库存导入失败:" + e.getMessage());
        }
        return R.ok("导入成功");
    }
    @Override
    public void exportStockInventory(HttpServletResponse response, StockInventoryDto stockInventoryDto) {
    public void exportStockInventory(HttpServletResponse response, StockInventoryDto stockInventoryDto, Integer productType) {
        List<StockInventoryExportData> list = stockInventoryMapper.listStockInventoryExportData(stockInventoryDto);
        ExcelUtil<StockInventoryExportData> util = new ExcelUtil<>(StockInventoryExportData.class);
        util.exportExcel(response, list, "库存信息");
        // 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
@@ -367,4 +588,48 @@
        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;
    }
}