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.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ruoyi.basic.mapper.ProductModelMapper; import com.ruoyi.basic.pojo.ProductModel; import com.ruoyi.common.enums.StockInQualifiedRecordTypeEnum; import com.ruoyi.common.enums.StockInUnQualifiedRecordTypeEnum; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.poi.ExcelUtil; 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.StockUninventoryDto; 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.service.StockInRecordService; import com.ruoyi.stock.service.StockInventoryService; import com.ruoyi.stock.service.StockOutRecordService; import com.ruoyi.stock.service.StockUninventoryService; import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.math.BigDecimal; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** *

* 库存表 服务实现类 *

* * @author 芯导软件(江苏)有限公司 * @since 2026-01-21 04:16:36 */ @Service @AllArgsConstructor public class StockInventoryServiceImpl extends ServiceImpl implements StockInventoryService { private StockInventoryMapper stockInventoryMapper; private StockInRecordService stockInRecordService; private StockOutRecordService stockOutRecordService; private StockUninventoryService stockUninventoryService; private SalesLedgerProductMapper salesLedgerProductMapper; private ProductModelMapper productModelMapper; @Override public IPage pagestockInventory(Page page, StockInventoryDto stockInventoryDto) { return stockInventoryMapper.pagestockInventory(page, stockInventoryDto); } @Override public IPage pageListCombinedStockInventory(Page page, StockInventoryDto stockInventoryDto) { return stockInventoryMapper.pageListCombinedStockInventory(page, stockInventoryDto); } //入库调用 @Override @Transactional(rollbackFor = Exception.class) public Boolean addstockInventory(StockInventoryDto stockInventoryDto) { String batchNo = StringUtils.trim(stockInventoryDto.getBatchNo()); if (StringUtils.isEmpty(batchNo)) { batchNo = generateAutoBatchNo(stockInventoryDto.getProductModelId()); } stockInventoryDto.setBatchNo(batchNo); LambdaQueryWrapper eq = new QueryWrapper().lambda() .eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId()); eq.eq(StockInventory::getBatchNo, stockInventoryDto.getBatchNo()); //新增入库记录再添加库存 StockInRecordDto stockInRecordDto = new StockInRecordDto(); stockInRecordDto.setRecordId(stockInventoryDto.getRecordId()); stockInRecordDto.setRecordType(stockInventoryDto.getRecordType()); stockInRecordDto.setStockInNum(stockInventoryDto.getQualitity()); stockInRecordDto.setBatchNo(stockInventoryDto.getBatchNo()); stockInRecordDto.setProductModelId(stockInventoryDto.getProductModelId()); stockInRecordDto.setType("0"); stockInRecordService.add(stockInRecordDto); //再进行新增库存数量库存 //先查询库存表中的产品是否存在,不存在新增,存在更新 StockInventory oldStockInventory = stockInventoryMapper.selectOne(eq); if (ObjectUtils.isEmpty(oldStockInventory)) { StockInventory newStockInventory = new StockInventory(); newStockInventory.setProductModelId(stockInventoryDto.getProductModelId()); newStockInventory.setQualitity(stockInventoryDto.getQualitity()); newStockInventory.setVersion(1); newStockInventory.setRemark(stockInventoryDto.getRemark()); newStockInventory.setBatchNo(stockInventoryDto.getBatchNo()); newStockInventory.setLockedQuantity(stockInventoryDto.getLockedQuantity()); newStockInventory.setWarnNum(stockInventoryDto.getWarnNum()); stockInventoryMapper.insert(newStockInventory); }else { stockInventoryMapper.updateAddStockInventory(stockInventoryDto); } return true; } //出库调用 @Override @Transactional(rollbackFor = Exception.class) public Boolean subtractStockInventory(StockInventoryDto stockInventoryDto) { LambdaQueryWrapper eq = new QueryWrapper().lambda() .eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId()); if (StringUtils.isEmpty(stockInventoryDto.getBatchNo())) { eq.isNull(StockInventory::getBatchNo); stockInventoryDto.setBatchNo(null); } else { eq.eq(StockInventory::getBatchNo, stockInventoryDto.getBatchNo()); } // 新增出库记录 StockOutRecordDto stockOutRecordDto = new StockOutRecordDto(); stockOutRecordDto.setRecordId(stockInventoryDto.getRecordId()); stockOutRecordDto.setRecordType(stockInventoryDto.getRecordType()); stockOutRecordDto.setStockOutNum(stockInventoryDto.getQualitity()); stockOutRecordDto.setBatchNo(stockInventoryDto.getBatchNo()); stockOutRecordDto.setProductModelId(stockInventoryDto.getProductModelId()); stockOutRecordDto.setType("0"); stockOutRecordService.add(stockOutRecordDto); StockInventory oldStockInventory = stockInventoryMapper.selectOne(eq); 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) { throw new RuntimeException("库存不足无法出库"); } stockInventoryMapper.updateSubtractStockInventory(stockInventoryDto); return true; } @Override @Transactional(rollbackFor = Exception.class) public Boolean addStockInRecordOnly(StockInventoryDto stockInventoryDto) { String batchNo = StringUtils.trim(stockInventoryDto.getBatchNo()); if (StringUtils.isEmpty(batchNo)) { batchNo = generateAutoBatchNo(stockInventoryDto.getProductModelId()); } stockInventoryDto.setBatchNo(batchNo); StockInRecordDto stockInRecordDto = new StockInRecordDto(); stockInRecordDto.setRecordId(stockInventoryDto.getRecordId()); stockInRecordDto.setRecordType(stockInventoryDto.getRecordType()); stockInRecordDto.setStockInNum(stockInventoryDto.getQualitity()); stockInRecordDto.setBatchNo(batchNo); stockInRecordDto.setProductModelId(stockInventoryDto.getProductModelId()); stockInRecordDto.setType("0"); stockInRecordDto.setRemark(stockInventoryDto.getRemark()); stockInRecordService.add(stockInRecordDto); return true; } //规则生成:20260424-产品编号-001 private String generateAutoBatchNo(Long productModelId) { if (productModelId == null) { throw new ServiceException("产品规格ID不能为空"); } ProductModel productModel = productModelMapper.selectById(productModelId); if (productModel == null) { throw new ServiceException("产品规格不存在,ID=" + productModelId); } String productCode = StringUtils.trim(productModel.getProductCode()); if (StringUtils.isEmpty(productCode)) { throw new ServiceException("产品规格未维护产品编码,ID=" + productModelId); } String dateText = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE); String prefix = dateText + "-" + productCode + "-"; int maxSequence = resolveMaxSequence(prefix); int sequence = maxSequence + 1; while (sequence < 1_000_000) { String batchNo = prefix + String.format("%03d", sequence); if (!isBatchNoExists(batchNo)) { return batchNo; } sequence++; } throw new ServiceException("批号序号超出范围,请检查批号数据"); } private int resolveMaxSequence(String prefix) { int maxSequence = 0; List stockInventoryList = stockInventoryMapper.selectList( Wrappers.lambdaQuery() .select(StockInventory::getBatchNo) .likeRight(StockInventory::getBatchNo, prefix)); for (StockInventory stockInventory : stockInventoryList) { maxSequence = Math.max(maxSequence, parseSequence(stockInventory.getBatchNo(), prefix)); } List stockInRecordList = stockInRecordService.list( Wrappers.lambdaQuery() .select(StockInRecord::getBatchNo) .likeRight(StockInRecord::getBatchNo, prefix)); for (StockInRecord stockInRecord : stockInRecordList) { maxSequence = Math.max(maxSequence, parseSequence(stockInRecord.getBatchNo(), prefix)); } return maxSequence; } private int parseSequence(String batchNo, String prefix) { if (StringUtils.isEmpty(batchNo) || StringUtils.isEmpty(prefix) || !batchNo.startsWith(prefix)) { return 0; } String sequenceText = batchNo.substring(prefix.length()); if (StringUtils.isEmpty(sequenceText) || !sequenceText.matches("\\d+")) { return 0; } try { return Integer.parseInt(sequenceText); } catch (NumberFormatException ignored) { return 0; } } private boolean isBatchNoExists(String batchNo) { if (StringUtils.isEmpty(batchNo)) { return false; } Long inventoryCount = stockInventoryMapper.selectCount( Wrappers.lambdaQuery().eq(StockInventory::getBatchNo, batchNo)); if (inventoryCount != null && inventoryCount > 0) { return true; } return stockInRecordService.count( Wrappers.lambdaQuery().eq(StockInRecord::getBatchNo, batchNo)) > 0; } @Override @Transactional(rollbackFor = Exception.class) public Boolean addStockOutRecordOnly(StockInventoryDto stockInventoryDto) { LambdaQueryWrapper eq = new LambdaQueryWrapper<>(); eq.eq(StockInventory::getProductModelId, stockInventoryDto.getProductModelId()); if (StringUtils.isEmpty(stockInventoryDto.getBatchNo())) { eq.isNull(StockInventory::getBatchNo); } else { eq.eq(StockInventory::getBatchNo, stockInventoryDto.getBatchNo()); } StockInventory stockInventory = stockInventoryMapper.selectOne(eq); if (stockInventory == null) { throw new ServiceException("库存记录不存在"); } BigDecimal lockedQty = stockInventory.getLockedQuantity(); if (lockedQty == null) { lockedQty = BigDecimal.ZERO; } BigDecimal pendingOut = stockInventoryMapper.selectPendingOutQuantity( stockInventoryDto.getProductModelId(), stockInventoryDto.getBatchNo(), "0" ); if (pendingOut == null) { pendingOut = BigDecimal.ZERO; } BigDecimal availableQty = stockInventory.getQualitity().subtract(lockedQty).subtract(pendingOut); if (stockInventoryDto.getQualitity().compareTo(availableQty) > 0) { throw new ServiceException("申请数量超过可用库存,当前可用库存为:" + availableQty); } StockOutRecordDto stockOutRecordDto = new StockOutRecordDto(); stockOutRecordDto.setRecordId(stockInventoryDto.getRecordId()); stockOutRecordDto.setRecordType(stockInventoryDto.getRecordType()); stockOutRecordDto.setStockOutNum(stockInventoryDto.getQualitity()); stockOutRecordDto.setBatchNo(stockInventoryDto.getBatchNo()); stockOutRecordDto.setProductModelId(stockInventoryDto.getProductModelId()); stockOutRecordDto.setType("0"); stockOutRecordDto.setRemark(stockInventoryDto.getRemark()); stockOutRecordService.add(stockOutRecordDto); return true; } @Override @Transactional(rollbackFor = Exception.class) public R importStockInventory(MultipartFile file) { try { // 查询所有的产品并构建映射,提高查找效率 List salesLedgerProducts = salesLedgerProductMapper.selectProduct(); Map productMap = new HashMap<>(); for (SalesLedgerProduct product : salesLedgerProducts) { // 使用产品类别和规格型号作为键 String key = product.getProductCategory() + "|" + product.getSpecificationModel(); productMap.put(key, product); } ExcelUtil util = new ExcelUtil(StockInventoryExportData.class); List list = util.importExcel(file.getInputStream()); // 记录未找到匹配项的数据 List unmatchedRecords = new ArrayList<>(); // 记录处理结果 int successCount = 0; for (StockInventoryExportData 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()); // 验证合格冻结数量 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()); // 验证不合格冻结数量 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("导入成功 " + successCount + " 条记录,以下产品未找到匹配项:\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) { List list = stockInventoryMapper.listStockInventoryExportData(stockInventoryDto); ExcelUtil util = new ExcelUtil<>(StockInventoryExportData.class); util.exportExcel(response,list, "库存信息"); } @Override public IPage stockInventoryPage(StockInventoryDto stockInventoryDto, Page page) { return stockInventoryMapper.stockInventoryPage(stockInventoryDto,page); } @Override public IPage 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); } @Override public List getByModelId(Long modelId) { return stockInventoryMapper.getByModelId(modelId); } }