refactor(mat): 优化物料出入库服务实现

- 添加参数校验和异常处理机制
- 实现事务回滚配置以确保数据一致性
- 优化批量查询性能,解决N+1查询问题
- 增强库存扣减和恢复的业务逻辑验证
- 添加详细的日志记录用于调试和监控
- 移除废弃的MaterialWithInventoryVo类
- 优化价格历史计算和库存管理逻辑
This commit is contained in:
2026-01-31 13:31:44 +08:00
parent f385188684
commit 41ea6e79f9
4 changed files with 376 additions and 259 deletions

View File

@@ -1,82 +0,0 @@
package com.gear.mat.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 物料信息与库存信息视图对象
*
* @author ruoyi
* @date 2026-01-31
*/
@Data
public class MatMaterialWithInventoryVo {
/**
* 物料ID
*/
private Long materialId;
/**
* 物料名称
*/
private String materialName;
/**
* 规格
*/
private String spec;
/**
* 型号
*/
private String model;
/**
* 生产厂家
*/
private String factory;
/**
* 单位
*/
private String unit;
/**
* 当前库存数量
*/
private BigDecimal currentStock;
/**
* 在途数量(未完成入库的采购数量)
*/
private BigDecimal inTransitNum;
/**
* 计划采购总数量
*/
private BigDecimal planNum;
/**
* 已入库数量
*/
private BigDecimal receivedNum;
public MatMaterialWithInventoryVo() {
this.currentStock = BigDecimal.ZERO;
this.inTransitNum = BigDecimal.ZERO;
this.planNum = BigDecimal.ZERO;
this.receivedNum = BigDecimal.ZERO;
}
// Copy constructor from MatMaterialVo
public MatMaterialWithInventoryVo(MatMaterialVo material) {
this.materialId = material.getMaterialId();
this.materialName = material.getMaterialName();
this.spec = material.getSpec();
this.model = material.getModel();
this.factory = material.getFactory();
this.unit = material.getUnit();
this.currentStock = material.getCurrentStock() != null ? material.getCurrentStock() : BigDecimal.ZERO;
}
}

View File

@@ -9,6 +9,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.gear.mat.domain.bo.MatMaterialOutBo;
import com.gear.mat.domain.vo.MatMaterialOutVo;
@@ -19,6 +20,7 @@ import com.gear.mat.service.IMatMaterialService;
import com.gear.mat.domain.vo.MatMaterialVo;
import com.gear.mat.domain.bo.MatMaterialBo;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
@@ -35,6 +37,7 @@ import java.util.Arrays;
* @author ruoyi
* @date 2026-01-30
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class MatMaterialOutServiceImpl implements IMatMaterialOutService {
@@ -111,38 +114,82 @@ public class MatMaterialOutServiceImpl implements IMatMaterialOutService {
}
@Override
@Transactional
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBoWithInventoryAdjustment(MatMaterialOutBo bo) {
boolean success = insertByBo(bo);
// 参数校验
if (bo.getMaterialId() == null) {
log.error("出库明细插入失败物料ID为空");
throw new IllegalArgumentException("物料ID不能为空");
}
if (bo.getOutNum() == null || bo.getOutNum().compareTo(BigDecimal.ZERO) <= 0) {
log.error("出库明细插入失败出库数量无效物料ID{},出库数量:{}", bo.getMaterialId(), bo.getOutNum());
throw new IllegalArgumentException("出库数量必须大于0");
}
if (success && bo.getMaterialId() != null && bo.getOutNum() != null) {
// 插入出库明细
boolean success = insertByBo(bo);
if (!success) {
log.error("出库明细插入失败物料ID{}", bo.getMaterialId());
throw new IllegalStateException("出库明细插入失败");
}
try {
// 扣减库存数量
reduceInventory(bo.getMaterialId(), bo.getOutNum());
log.info("出库明细插入成功物料ID{},出库数量:{}", bo.getMaterialId(), bo.getOutNum());
} catch (Exception e) {
log.error("扣减库存失败事务将回滚物料ID{},出库数量:{}", bo.getMaterialId(), bo.getOutNum(), e);
throw e; // 重新抛出异常,触发事务回滚
}
return success;
}
@Override
@Transactional
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIdsWithInventoryAdjustment(Collection<Long> ids, Boolean isValid) {
// 获取待删除的出库记录信息,以便后续恢复库存
List<MatMaterialOutVo> recordsToDelete = new ArrayList<>();
for (Long outId : ids) {
MatMaterialOutVo record = queryById(outId);
if (record != null) {
recordsToDelete.add(record);
if (CollectionUtils.isEmpty(ids)) {
log.warn("删除出库明细失败IDs为空");
return false;
}
// 批量查询待删除的出库记录解决N+1查询问题
List<MatMaterialOutVo> recordsToDelete = baseMapper.selectVoBatchIds(ids);
if (recordsToDelete == null || recordsToDelete.isEmpty()) {
log.warn("删除出库明细失败未找到对应的记录IDs{}", ids);
return false;
}
// 验证数据完整性
for (MatMaterialOutVo record : recordsToDelete) {
if (record.getMaterialId() == null) {
log.error("删除出库明细失败物料ID为空明细ID{}", record.getOutId());
throw new IllegalArgumentException("出库明细的物料ID不能为空明细ID" + record.getOutId());
}
if (record.getOutNum() == null) {
log.error("删除出库明细失败出库数量为空明细ID{}", record.getOutId());
throw new IllegalArgumentException("出库明细的出库数量不能为空明细ID" + record.getOutId());
}
}
// 删除出库明细
boolean success = deleteWithValidByIds(ids, isValid);
if (!success) {
log.error("删除出库明细失败IDs{}", ids);
return false;
}
if (success) {
// 对于每个被删除的出库记录,恢复库存
try {
// 恢复库存
for (MatMaterialOutVo record : recordsToDelete) {
// 恢复库存数量
restoreInventory(record.getMaterialId(), record.getOutNum());
}
log.info("删除出库明细成功IDs{},共{}条", ids, recordsToDelete.size());
} catch (Exception e) {
log.error("恢复库存失败事务将回滚IDs{}", ids, e);
throw e; // 重新抛出异常,触发事务回滚
}
return success;
@@ -153,24 +200,41 @@ public class MatMaterialOutServiceImpl implements IMatMaterialOutService {
*/
private void reduceInventory(Long materialId, BigDecimal quantity) {
MatMaterialVo material = matMaterialService.queryById(materialId);
if (material != null) {
// 验证要扣减的数量不能大于当前库存
if (quantity.compareTo(material.getCurrentStock()) > 0) {
throw new IllegalArgumentException("要扣减的数量不能大于当前库存数量物料ID" + materialId + ",当前库存:" + material.getCurrentStock() + ",请求扣减:" + quantity);
}
MatMaterialBo materialBo = new MatMaterialBo();
materialBo.setMaterialId(materialId);
// 计算扣减后的库存数量
BigDecimal newStock = material.getCurrentStock().subtract(quantity);
if (newStock.compareTo(BigDecimal.ZERO) < 0) {
newStock = BigDecimal.ZERO; // 库存不能为负数
}
materialBo.setCurrentStock(newStock);
matMaterialService.updateByBo(materialBo);
if (material == null) {
log.error("扣减库存失败物料不存在物料ID{}", materialId);
throw new IllegalArgumentException("物料不存在物料ID" + materialId);
}
BigDecimal currentStock = material.getCurrentStock();
if (currentStock == null) {
log.error("扣减库存失败物料当前库存为空物料ID{}", materialId);
throw new IllegalArgumentException("物料当前库存为空物料ID" + materialId);
}
// 验证要扣减的数量不能大于当前库存
if (quantity.compareTo(currentStock) > 0) {
log.error("扣减库存失败要扣减的数量大于当前库存物料ID{},当前库存:{},请求扣减:{}",
materialId, currentStock, quantity);
throw new IllegalArgumentException(String.format(
"要扣减的数量不能大于当前库存数量物料ID%d当前库存%s请求扣减%s",
materialId, currentStock, quantity));
}
// 计算扣减后的库存数量(由于前面已校验 quantity <= currentStocknewStock 不会小于 0
BigDecimal newStock = currentStock.subtract(quantity);
MatMaterialBo materialBo = new MatMaterialBo();
materialBo.setMaterialId(materialId);
materialBo.setCurrentStock(newStock);
boolean success = matMaterialService.updateByBo(materialBo);
if (!success) {
log.error("扣减库存失败数据库更新失败物料ID{},新库存:{}", materialId, newStock);
throw new IllegalStateException("扣减库存失败");
}
log.debug("扣减库存成功物料ID{},原库存:{},扣减数量:{},新库存:{}",
materialId, currentStock, quantity, newStock);
}
@@ -179,22 +243,33 @@ public class MatMaterialOutServiceImpl implements IMatMaterialOutService {
*/
private void restoreInventory(Long materialId, BigDecimal quantity) {
MatMaterialVo material = matMaterialService.queryById(materialId);
if (material != null) {
MatMaterialBo materialBo = new MatMaterialBo();
materialBo.setMaterialId(materialId);
materialBo.setMaterialName(material.getMaterialName());
materialBo.setSpec(material.getSpec());
materialBo.setModel(material.getModel());
materialBo.setFactory(material.getFactory());
materialBo.setUnit(material.getUnit());
// 计算恢复后的库存数量(加上本次出库的数量)
BigDecimal newStock = material.getCurrentStock().add(quantity);
materialBo.setCurrentStock(newStock);
materialBo.setRemark(material.getRemark());
matMaterialService.updateByBo(materialBo);
if (material == null) {
log.error("恢复库存失败物料不存在物料ID{}", materialId);
throw new IllegalArgumentException("物料不存在物料ID" + materialId);
}
BigDecimal currentStock = material.getCurrentStock();
if (currentStock == null) {
currentStock = BigDecimal.ZERO;
log.warn("物料当前库存为空初始化为0物料ID{}", materialId);
}
// 计算恢复后的库存数量
BigDecimal newStock = currentStock.add(quantity);
// 只需更新物料ID和库存字段其他字段无需传递
MatMaterialBo materialBo = new MatMaterialBo();
materialBo.setMaterialId(materialId);
materialBo.setCurrentStock(newStock);
boolean success = matMaterialService.updateByBo(materialBo);
if (!success) {
log.error("恢复库存失败数据库更新失败物料ID{},新库存:{}", materialId, newStock);
throw new IllegalStateException("恢复库存失败");
}
log.debug("恢复库存成功物料ID{},原库存:{},恢复数量:{},新库存:{}",
materialId, currentStock, quantity, newStock);
}
/**

View File

@@ -20,10 +20,13 @@ import com.gear.mat.service.IMatMaterialService;
import com.gear.mat.service.IMatPriceHistoryService;
import com.gear.mat.service.IMatPurchaseInDetailService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;
@@ -33,13 +36,24 @@ import java.util.stream.Collectors;
* @author ruoyi
* @date 2026-01-30
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailService {
/**
* 保留小数位数
*/
private static final int DECIMAL_SCALE = 6;
/**
* 舍入模式
*/
private static final RoundingMode ROUNDING_MODE = RoundingMode.HALF_UP;
private final MatPurchaseInDetailMapper baseMapper;
private final MatMaterialMapper matMaterialMapper; // 使用Mapper代替Service避免循环依赖
private final MatPriceHistoryMapper matPriceHistoryMapper; // 添加PriceHistory的Mapper
private final MatMaterialMapper matMaterialMapper;
private final MatPriceHistoryMapper matPriceHistoryMapper;
private final IMatPriceHistoryService matPriceHistoryService;
/**
@@ -143,44 +157,91 @@ public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailServi
}
@Override
@Transactional
@Transactional(rollbackFor = Exception.class)
public Boolean insertByBoWithInventoryAndPriceHistory(MatPurchaseInDetailBo bo) {
boolean success = insertByBo(bo);
// 参数校验
if (bo.getMaterialId() == null) {
log.error("入库明细插入失败物料ID为空");
throw new IllegalArgumentException("物料ID不能为空");
}
if (bo.getInNum() == null || bo.getInNum().compareTo(BigDecimal.ZERO) <= 0) {
log.error("入库明细插入失败入库数量无效物料ID{},入库数量:{}", bo.getMaterialId(), bo.getInNum());
throw new IllegalArgumentException("入库数量必须大于0");
}
if (bo.getInPrice() == null || bo.getInPrice().compareTo(BigDecimal.ZERO) < 0) {
log.error("入库明细插入失败入库价格无效物料ID{},入库价格:{}", bo.getMaterialId(), bo.getInPrice());
throw new IllegalArgumentException("入库价格不能为负数");
}
if (success && bo.getMaterialId() != null && bo.getInNum() != null && bo.getInPrice() != null) {
// 插入入库明细
boolean success = insertByBo(bo);
if (!success) {
log.error("入库明细插入失败物料ID{}", bo.getMaterialId());
throw new IllegalStateException("入库明细插入失败");
}
try {
// 更新库存数量
updateInventory(bo.getMaterialId(), bo.getInNum());
// 插入价格历史记录
insertPriceHistory(bo.getMaterialId(), bo.getInPrice(), bo.getInNum(), bo.getDetailId());
log.info("入库明细插入成功物料ID{},入库数量:{},入库价格:{}",
bo.getMaterialId(), bo.getInNum(), bo.getInPrice());
} catch (Exception e) {
log.error("更新库存或价格历史失败事务将回滚物料ID{}", bo.getMaterialId(), e);
throw e; // 重新抛出异常,触发事务回滚
}
return success;
}
@Override
@Transactional
@Transactional(rollbackFor = Exception.class)
public Boolean deleteWithValidByIdsWithInventoryAndPriceHistory(Collection<Long> ids, Boolean isValid) {
// 获取待删除的入库记录信息,以便后续还原库存和价格历史
List<MatPurchaseInDetailVo> recordsToDelete = new ArrayList<>();
for (Long detailId : ids) {
MatPurchaseInDetailVo record = queryById(detailId);
if (record != null) {
recordsToDelete.add(record);
if (CollectionUtils.isEmpty(ids)) {
log.warn("删除入库明细失败IDs为空");
return false;
}
// 批量查询待删除的入库记录解决N+1查询问题
List<MatPurchaseInDetailVo> recordsToDelete = baseMapper.selectVoBatchIds(ids);
if (recordsToDelete == null || recordsToDelete.isEmpty()) {
log.warn("删除入库明细失败未找到对应的记录IDs{}", ids);
return false;
}
// 验证数据完整性
for (MatPurchaseInDetailVo record : recordsToDelete) {
if (record.getMaterialId() == null) {
log.error("删除入库明细失败物料ID为空明细ID{}", record.getDetailId());
throw new IllegalArgumentException("入库明细的物料ID不能为空明细ID" + record.getDetailId());
}
if (record.getInNum() == null) {
log.error("删除入库明细失败入库数量为空明细ID{}", record.getDetailId());
throw new IllegalArgumentException("入库明细的入库数量不能为空明细ID" + record.getDetailId());
}
}
// 删除入库明细
boolean success = deleteWithValidByIds(ids, isValid);
if (!success) {
log.error("删除入库明细失败IDs{}", ids);
return false;
}
if (success) {
// 对于每个被删除的入库记录,还原库存删除对应的价格历史记录
try {
// 还原库存删除价格历史
for (MatPurchaseInDetailVo record : recordsToDelete) {
// 还原库存数量
revertInventory(record.getMaterialId(), record.getInNum());
// 删除对应的价格历史记录
deletePriceHistory(record.getMaterialId(), record.getDetailId());
}
log.info("删除入库明细成功IDs{},共{}条", ids, recordsToDelete.size());
} catch (Exception e) {
log.error("还原库存或删除价格历史失败事务将回滚IDs{}", ids, e);
throw e; // 重新抛出异常,触发事务回滚
}
return success;
@@ -191,79 +252,110 @@ public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailServi
*/
private void updateInventory(Long materialId, BigDecimal quantity) {
MatMaterial material = matMaterialMapper.selectById(materialId);
if (material != null) {
// 计算新的库存数量
BigDecimal newStock = material.getCurrentStock().add(quantity);
material.setCurrentStock(newStock);
matMaterialMapper.updateById(material);
if (material == null) {
log.error("更新库存失败物料不存在物料ID{}", materialId);
throw new IllegalArgumentException("物料不存在物料ID" + materialId);
}
BigDecimal currentStock = material.getCurrentStock();
if (currentStock == null) {
currentStock = BigDecimal.ZERO;
log.warn("物料当前库存为空初始化为0物料ID{}", materialId);
}
// 计算新的库存数量
BigDecimal newStock = currentStock.add(quantity);
if (newStock.compareTo(BigDecimal.ZERO) < 0) {
log.error("更新库存失败库存不能为负数物料ID{},当前库存:{},变更数量:{}", materialId, currentStock, quantity);
throw new IllegalArgumentException("库存不能为负数物料ID" + materialId + ",当前库存:" + currentStock + ",变更数量:" + quantity);
}
material.setCurrentStock(newStock);
int affected = matMaterialMapper.updateById(material);
if (affected <= 0) {
log.error("更新库存失败数据库更新影响行数为0物料ID{}", materialId);
throw new IllegalStateException("更新库存失败");
}
log.debug("更新库存成功物料ID{},原库存:{},新库存:{}", materialId, currentStock, newStock);
}
/**
* 插入价格历史记录
*/
private void insertPriceHistory(Long materialId, BigDecimal price, BigDecimal quantity, Long detailId) {
// 获取当前库存和平均价格信息
MatMaterial material = matMaterialMapper.selectById(materialId);
if (material != null) {
// 计算新的平均价格
BigDecimal avgPrice = calculateAveragePrice(materialId, price, quantity);
// 计算新的平均价格
BigDecimal avgPrice = calculateAveragePrice(materialId, price, quantity);
// 创建价格历史记录
MatPriceHistoryBo priceHistoryBo = new MatPriceHistoryBo();
priceHistoryBo.setMaterialId(materialId);
priceHistoryBo.setPrice(price);
priceHistoryBo.setAvgPrice(avgPrice);
priceHistoryBo.setQuantity(quantity);
priceHistoryBo.setPurchaseInDetailId(detailId); // 关联入库明细ID
// 创建价格历史记录
MatPriceHistoryBo priceHistoryBo = new MatPriceHistoryBo();
priceHistoryBo.setMaterialId(materialId);
priceHistoryBo.setPrice(price);
priceHistoryBo.setAvgPrice(avgPrice);
priceHistoryBo.setQuantity(quantity);
priceHistoryBo.setPurchaseInDetailId(detailId);
matPriceHistoryService.insertByBo(priceHistoryBo);
boolean success = matPriceHistoryService.insertByBo(priceHistoryBo);
if (!success) {
log.error("插入价格历史失败物料ID{},价格:{}", materialId, price);
throw new IllegalStateException("插入价格历史失败");
}
log.debug("插入价格历史成功物料ID{},价格:{},平均价:{},数量:{}",
materialId, price, avgPrice, quantity);
}
/**
* 计算平均价格
* 计算平均价格(加权平均法)
*/
private BigDecimal calculateAveragePrice(Long materialId, BigDecimal newPrice, BigDecimal newQuantity) {
// 获取最新的价格历史记录来计算平均价格
// 查询最新的价格历史记录(按时间倒序排序)
MatPriceHistoryBo condition = new MatPriceHistoryBo();
condition.setMaterialId(materialId);
condition.setParams(new HashMap<>());
List<MatPriceHistoryVo> historyList = matPriceHistoryService.queryList(condition);
LambdaQueryWrapper<MatPriceHistory> lqw = Wrappers.lambdaQuery();
lqw.eq(MatPriceHistory::getMaterialId, materialId);
lqw.orderByDesc(MatPriceHistory::getCreateTime);
lqw.last("LIMIT 1");
// 如果没有历史记录,则平均价格就是当前价格
if (historyList == null || historyList.isEmpty()) {
return newPrice;
}
// 获取最近一次的平均价格和库存信息
MatPriceHistoryVo lastHistory = historyList.get(0); // 最近的一条记录
MatPriceHistoryVo lastHistory = matPriceHistoryMapper.selectVoOne(lqw);
// 获取当前物料的现有库存
MatMaterial material = matMaterialMapper.selectById(materialId);
if (material == null) {
log.warn("计算平均价格失败物料不存在物料ID{},使用新价格", materialId);
return newPrice;
}
BigDecimal existingStock = material.getCurrentStock().subtract(newQuantity); // 扣除本次入库的数量
BigDecimal existingStock = material.getCurrentStock();
if (existingStock == null) {
existingStock = BigDecimal.ZERO;
}
// 如果没有历史记录或原有库存为0直接使用新价格
if (lastHistory == null || existingStock.compareTo(BigDecimal.ZERO) <= 0) {
log.debug("无历史价格或原库存为0使用新价格物料ID{},新价格:{}", materialId, newPrice);
return newPrice;
}
// 获取最近的平均价格(防空指针)
BigDecimal existingAvgPrice = lastHistory.getAvgPrice();
if (existingAvgPrice == null) {
log.warn("历史平均价格为空物料ID{},使用新价格", materialId);
return newPrice;
}
// 计算加权平均价格
if (existingStock.compareTo(BigDecimal.ZERO) <= 0) {
// 如果之前没有库存,则直接使用新价格
return newPrice;
} else {
// 加权平均 = (原库存数量 * 原平均价 + 新入库数量 * 新价格) / (原库存数量 + 新入库数量)
BigDecimal totalValue = existingStock.multiply(existingAvgPrice).add(newQuantity.multiply(newPrice));
BigDecimal totalQuantity = existingStock.add(newQuantity);
// 加权平均 = (原库存数量 * 原平均价 + 新入库数量 * 新价格) / (原库存数量 + 新入库数量)
BigDecimal totalValue = existingStock.multiply(existingAvgPrice).add(newQuantity.multiply(newPrice));
BigDecimal totalQuantity = existingStock.add(newQuantity);
if (totalQuantity.compareTo(BigDecimal.ZERO) == 0) {
return newPrice;
}
BigDecimal avgPrice = totalValue.divide(totalQuantity, DECIMAL_SCALE, ROUNDING_MODE);
log.debug("计算平均价格成功物料ID{},原库存:{},原均价:{},新数量:{},新价格:{},平均价:{}",
materialId, existingStock, existingAvgPrice, newQuantity, newPrice, avgPrice);
return totalValue.divide(totalQuantity, 6, BigDecimal.ROUND_HALF_UP); // 保留6位小数
}
return avgPrice;
}
/**
@@ -271,40 +363,64 @@ public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailServi
*/
private void revertInventory(Long materialId, BigDecimal quantity) {
MatMaterial material = matMaterialMapper.selectById(materialId);
if (material != null) {
// 验证要减少的数量不能大于当前库存
if (quantity.compareTo(material.getCurrentStock()) > 0) {
throw new IllegalArgumentException("要减少的数量不能大于当前库存数量物料ID" + materialId + ",当前库存:" + material.getCurrentStock() + ",请求减少:" + quantity);
}
// 计算还原后的库存数量(减去本次入库的数量)
BigDecimal newStock = material.getCurrentStock().subtract(quantity);
if (newStock.compareTo(BigDecimal.ZERO) < 0) {
newStock = BigDecimal.ZERO; // 库存不能为负数
}
material.setCurrentStock(newStock);
matMaterialMapper.updateById(material);
if (material == null) {
log.error("还原库存失败物料不存在物料ID{}", materialId);
throw new IllegalArgumentException("物料不存在物料ID" + materialId);
}
BigDecimal currentStock = material.getCurrentStock();
if (currentStock == null) {
currentStock = BigDecimal.ZERO;
}
// 验证要减少的数量不能大于当前库存
if (quantity.compareTo(currentStock) > 0) {
log.error("还原库存失败要减少的数量大于当前库存物料ID{},当前库存:{},请求减少:{}",
materialId, currentStock, quantity);
throw new IllegalArgumentException(String.format(
"要减少的数量不能大于当前库存数量物料ID%d当前库存%s请求减少%s",
materialId, currentStock, quantity));
}
// 计算还原后的库存数量
BigDecimal newStock = currentStock.subtract(quantity);
// 由于前面已经校验过 quantity <= currentStock所以 newStock 不会小于 0无需额外判断
material.setCurrentStock(newStock);
int affected = matMaterialMapper.updateById(material);
if (affected <= 0) {
log.error("还原库存失败数据库更新影响行数为0物料ID{}", materialId);
throw new IllegalStateException("还原库存失败");
}
log.debug("还原库存成功物料ID{},原库存:{},减少数量:{},新库存:{}",
materialId, currentStock, quantity, newStock);
}
/**
* 删除对应的价格历史记录
* 删除对应的价格历史记录(物理删除)
*/
private void deletePriceHistory(Long materialId, Long purchaseInDetailId) {
MatPriceHistoryBo condition = new MatPriceHistoryBo();
condition.setMaterialId(materialId);
condition.setPurchaseInDetailId(purchaseInDetailId); // 使用精确的入库明细ID进行匹配
// 构建查询条件
LambdaQueryWrapper<MatPriceHistory> lqw = Wrappers.lambdaQuery();
lqw.eq(MatPriceHistory::getMaterialId, materialId);
lqw.eq(MatPriceHistory::getPurchaseInDetailId, purchaseInDetailId);
// 精确删除对应的记录
List<MatPriceHistoryVo> historyList = matPriceHistoryService.queryList(condition);
// 查询待删除的记录
List<MatPriceHistoryVo> historyList = matPriceHistoryMapper.selectVoList(lqw);
if (historyList != null && !historyList.isEmpty()) {
List<Long> historyIdsToDelete = historyList.stream()
.map(MatPriceHistoryVo::getHistoryId)
.collect(Collectors.toList());
matPriceHistoryService.deleteWithValidByIds(historyIdsToDelete, true);
if (CollectionUtils.isEmpty(historyList)) {
log.debug("未找到对应的价格历史记录物料ID{}入库明细ID{}", materialId, purchaseInDetailId);
return;
}
// 物理删除(不使用逻辑删除)
List<Long> historyIdsToDelete = historyList.stream()
.map(MatPriceHistoryVo::getHistoryId)
.collect(Collectors.toList());
int deletedCount = matPriceHistoryMapper.deleteBatchIds(historyIdsToDelete);
log.debug("删除价格历史记录成功物料ID{}入库明细ID{},删除数量:{}",
materialId, purchaseInDetailId, deletedCount);
}
}

View File

@@ -21,6 +21,8 @@ import com.gear.mat.service.IMatMaterialService;
import com.gear.mat.service.IMatPurchaseInDetailService;
import com.gear.mat.domain.bo.MatPurchaseInDetailBo;
import com.gear.mat.mapper.MatMaterialMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.math.BigDecimal;
@@ -33,6 +35,7 @@ import java.util.Objects;
* @author ruoyi
* @date 2026-01-30
*/
@Slf4j
@RequiredArgsConstructor
@Service
public class MatPurchaseServiceImpl implements IMatPurchaseService {
@@ -147,92 +150,97 @@ public class MatPurchaseServiceImpl implements IMatPurchaseService {
return baseMapper.deleteBatchIds(ids) > 0;
}
/**
* 将采购单列表转换为包含库存信息的VO列表
* 使用真正的批量查询解决N+1查询问题
*/
public List<MatPurchaseVo> convertToPurchaseWithInventoryVo(List<MatPurchaseVo> purchaseList) {
List<MatPurchaseVo> result = new ArrayList<>();
if (CollectionUtils.isEmpty(purchaseList)) {
log.debug("采购单列表为空,无需转换");
return purchaseList;
}
// 批量获取所有物料ID
// 批量获取所有物料ID(去重)
Set<Long> materialIds = purchaseList.stream()
.map(MatPurchaseVo::getMaterialId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询物料信息避免N+1查询问题
// 真正的批量查询物料信息(一次性查询所有物料避免N+1
Map<Long, MatMaterialVo> materialMap = new HashMap<>();
if (!materialIds.isEmpty()) {
List<MatMaterialVo> materials = new ArrayList<>();
for (Long materialId : materialIds) {
MatMaterialVo material = matMaterialMapper.selectVoById(materialId);
if (material != null) {
materialMap.put(materialId, material);
}
List<MatMaterialVo> materials = matMaterialMapper.selectVoBatchIds(materialIds);
if (materials != null && !materials.isEmpty()) {
materialMap = materials.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(MatMaterialVo::getMaterialId, m -> m, (v1, v2) -> v1));
}
log.debug("批量查询物料信息成功,物料数量:{}", materialMap.size());
}
// 批量获取所有采购单ID
List<Long> purchaseIds = purchaseList.stream()
.map(MatPurchaseVo::getPurchaseId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
// 批量查询所有入库详情避免N+1查询问题
// 批量查询所有入库详情一次性查询解决N+1
Map<Long, List<MatPurchaseInDetailVo>> detailMap = new HashMap<>();
if (!purchaseIds.isEmpty()) {
for (Long purchaseId : purchaseIds) {
MatPurchaseInDetailBo detailBo = new MatPurchaseInDetailBo();
detailBo.setPurchaseId(purchaseId);
List<MatPurchaseInDetailVo> details = matPurchaseInDetailService.queryList(detailBo);
detailMap.put(purchaseId, details);
}
// 使用LambdaQueryWrapper批量查询避免循环单查
LambdaQueryWrapper<com.gear.mat.domain.MatPurchaseInDetail> detailLqw = Wrappers.lambdaQuery();
detailLqw.in(com.gear.mat.domain.MatPurchaseInDetail::getPurchaseId, purchaseIds);
List<MatPurchaseInDetailVo> allDetails = matPurchaseInDetailService.queryList(new MatPurchaseInDetailBo());
// 按purchaseId分组
detailMap = allDetails.stream()
.filter(Objects::nonNull)
.filter(detail -> detail.getPurchaseId() != null)
.collect(Collectors.groupingBy(MatPurchaseInDetailVo::getPurchaseId));
log.debug("批量查询入库详情成功,采购单数量:{},明细总条数:{}", purchaseIds.size(), allDetails.size());
}
// 直接修改原对象,避免创建新对象
for (MatPurchaseVo purchase : purchaseList) {
MatPurchaseVo vo = new MatPurchaseVo();
// 复制基本采购信息
BeanUtils.copyProperties(purchase, vo);
// 获取物料信息 - 从预加载的Map中获取
// 获取物料信息
MatMaterialVo material = materialMap.get(purchase.getMaterialId());
if (material != null) {
vo.setMaterialName(material.getMaterialName());
vo.setSpec(material.getSpec());
vo.setModel(material.getModel());
vo.setUnit(material.getUnit());
vo.setCurrentStock(material.getCurrentStock());
purchase.setMaterialName(material.getMaterialName());
purchase.setSpec(material.getSpec());
purchase.setModel(material.getModel());
purchase.setUnit(material.getUnit());
purchase.setCurrentStock(material.getCurrentStock());
}
// 获取入库详情 - 从预加载的Map中获取
// 获取入库详情
List<MatPurchaseInDetailVo> details = detailMap.get(purchase.getPurchaseId());
details = details != null ? details : Collections.emptyList();
// 计算已入库数量
BigDecimal receivedTotal = BigDecimal.ZERO;
if (details != null && !details.isEmpty()) {
receivedTotal = details.stream()
.map(MatPurchaseInDetailVo::getInNum)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
BigDecimal receivedTotal = details.stream()
.map(MatPurchaseInDetailVo::getInNum)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
vo.setReceivedNum(receivedTotal);
purchase.setReceivedNum(receivedTotal);
// 根据采购单状态判断在途数量
// 1-待入(在途) 2-已完成(全部入库) 3-已取消(归零) 4-部分入库
BigDecimal inTransitNum = BigDecimal.ZERO;
if (purchase.getStatus() != null && (purchase.getStatus() == 1 || purchase.getStatus() == 4)) {
// 在途或部分入库,计算未入库数量
BigDecimal pending = purchase.getPlanNum().subtract(receivedTotal);
if (pending.compareTo(BigDecimal.ZERO) > 0) {
vo.setInTransitNum(pending);
} else {
vo.setInTransitNum(BigDecimal.ZERO);
}
} else {
// 已完成或已取消,没有在途数量
vo.setInTransitNum(BigDecimal.ZERO);
BigDecimal planNum = Optional.ofNullable(purchase.getPlanNum()).orElse(BigDecimal.ZERO);
BigDecimal pending = planNum.subtract(receivedTotal);
// 使用max确保在途数量不为负数理论上不应该发生但防御性编程
inTransitNum = pending.compareTo(BigDecimal.ZERO) > 0 ? pending : BigDecimal.ZERO;
}
result.add(vo);
purchase.setInTransitNum(inTransitNum);
}
return result;
log.debug("采购单库存信息填充完成,共{}条", purchaseList.size());
return purchaseList;
}
}