refactor(purchase): 解决循环依赖问题

- 从 IMatPurchaseService 接口中移除 queryMaterialInventory 和 queryPurchasePlanMaterialInventory 方法
- 将库存查询实现从 MatPurchaseServiceImpl 迁移到 MatMaterialServiceImpl
- 修改 MatMaterialServiceImpl 中的依赖注入,使用 MatPurchaseMapper 替代 IMatPurchaseService
- 更新控制器 MatPurchaseController,移除库存相关 API 端点
- 优化 MatPurchaseServiceImpl 中的查询逻辑,改用批量查询减少 N+1 问题
- 在 MatMaterialServiceImpl 中实现完整的物料库存查询功能,包括当前库存、在途数量等信息
This commit is contained in:
2026-01-31 11:38:35 +08:00
parent ab8e31d158
commit e5bef20d8e
5 changed files with 181 additions and 239 deletions

View File

@@ -102,34 +102,4 @@ public class MatPurchaseController extends BaseController {
return toAjax(iMatPurchaseService.deleteWithValidByIds(Arrays.asList(purchaseIds), true));
}
/**
* 获取物料库存信息(当前库存和在途数量)
*
* @param materialId 物料ID
*/
@GetMapping("/inventory/{materialId}")
public R<List<MaterialInventoryVo>> getMaterialInventory(@PathVariable Long materialId) {
List<MaterialInventoryVo> inventoryList = iMatPurchaseService.queryMaterialInventory(materialId);
return R.ok(inventoryList);
}
/**
* 获取所有物料库存信息(当前库存和在途数量)
*/
@GetMapping("/inventory")
public R<List<MaterialInventoryVo>> getAllMaterialInventory() {
List<MaterialInventoryVo> inventoryList = iMatPurchaseService.queryMaterialInventory(null);
return R.ok(inventoryList);
}
/**
* 获取采购计划中所有物料的库存信息(当前库存和在途数量)
*
* @param purchaseId 采购单ID
*/
@GetMapping("/plan-inventory/{purchaseId}")
public R<List<PurchasePlanMaterialInventoryVo>> getPurchasePlanMaterialInventory(@PathVariable Long purchaseId) {
List<PurchasePlanMaterialInventoryVo> inventoryList = iMatPurchaseService.queryPurchasePlanMaterialInventory(purchaseId);
return R.ok(inventoryList);
}
}

View File

@@ -48,13 +48,4 @@ public interface IMatPurchaseService {
*/
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
/**
* 查询物料库存信息(当前库存和在途数量)
*/
List<MaterialInventoryVo> queryMaterialInventory(Long materialId);
/**
* 查询采购计划中所有物料的库存信息(当前库存和在途数量)
*/
List<PurchasePlanMaterialInventoryVo> queryPurchasePlanMaterialInventory(Long purchaseId);
}

View File

@@ -1,27 +1,32 @@
package com.gear.mat.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gear.common.utils.StringUtils;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.gear.mat.domain.vo.MaterialInventoryVo;
import com.gear.mat.domain.MatPurchase;
import com.gear.mat.domain.bo.MatPurchaseBo;
import com.gear.mat.domain.bo.MatPurchaseInDetailBo;
import com.gear.mat.domain.vo.*;
import com.gear.mat.mapper.MatPurchaseMapper;
import com.gear.mat.service.IMatPurchaseInDetailService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import com.gear.mat.domain.bo.MatMaterialBo;
import com.gear.mat.domain.vo.MatMaterialVo;
import com.gear.mat.domain.vo.MatMaterialWithInventoryVo;
import com.gear.mat.domain.MatMaterial;
import com.gear.mat.mapper.MatMaterialMapper;
import com.gear.mat.service.IMatMaterialService;
import com.gear.mat.service.IMatPurchaseService;
import java.util.List;
import java.util.Map;
import java.util.Collection;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
import java.util.Objects;
/**
* 配料配件基础信息Service业务层处理
@@ -34,7 +39,8 @@ import java.util.stream.Collectors;
public class MatMaterialServiceImpl implements IMatMaterialService {
private final MatMaterialMapper baseMapper;
private final IMatPurchaseService matPurchaseService;
private final MatPurchaseMapper matPurchaseMapper;
private final IMatPurchaseInDetailService matPurchaseInDetailService;
/**
* 查询配料配件基础信息
@@ -128,7 +134,7 @@ public class MatMaterialServiceImpl implements IMatMaterialService {
MatMaterialWithInventoryVo inventoryVo = new MatMaterialWithInventoryVo(material);
// 查询该物料的库存信息
List<MaterialInventoryVo> materialInventoryList = matPurchaseService.queryMaterialInventory(material.getMaterialId());
List<MaterialInventoryVo> materialInventoryList = queryMaterialInventory(material.getMaterialId());
if (!materialInventoryList.isEmpty()) {
MaterialInventoryVo inventoryInfo = materialInventoryList.get(0);
@@ -159,7 +165,7 @@ public class MatMaterialServiceImpl implements IMatMaterialService {
MatMaterialWithInventoryVo inventoryVo = new MatMaterialWithInventoryVo(material);
// 查询该物料的库存信息
List<MaterialInventoryVo> materialInventoryList = matPurchaseService.queryMaterialInventory(material.getMaterialId());
List<MaterialInventoryVo> materialInventoryList = queryMaterialInventory(material.getMaterialId());
if (!materialInventoryList.isEmpty()) {
MaterialInventoryVo inventoryInfo = materialInventoryList.get(0);
@@ -172,4 +178,95 @@ public class MatMaterialServiceImpl implements IMatMaterialService {
})
.collect(Collectors.toList());
}
public List<MaterialInventoryVo> queryMaterialInventory(Long materialId) {
// 创建结果列表
List<MaterialInventoryVo> result = new ArrayList<>();
// 构建采购单查询条件直接用QueryWrapper和Service层逻辑保持一致
QueryWrapper<MatPurchase> purchaseQw = new QueryWrapper<>();
if (materialId != null) {
purchaseQw.eq("material_id", materialId);
}
// 直接查采购单PO
List<MatPurchase> purchaseListPo = matPurchaseMapper.selectList(purchaseQw);
// 将PO转换为VO以便后续处理
List<MatPurchaseVo> purchaseList = purchaseListPo.stream()
.map(purchase -> {
MatPurchaseVo vo = new MatPurchaseVo();
BeanUtils.copyProperties(purchase, vo);
return vo;
})
.collect(Collectors.toList());
// 按物料ID分组
Map<Long, List<MatPurchaseVo>> purchaseMap = purchaseList.stream()
.collect(Collectors.groupingBy(MatPurchaseVo::getMaterialId));
for (Long matId : purchaseMap.keySet()) {
// 通过采购单中的物料ID查询物料详细信息
MatMaterialVo material = baseMapper.selectVoById(matId);
MaterialInventoryVo inventory = new MaterialInventoryVo();
inventory.setMaterialId(matId);
if (material != null) {
inventory.setMaterialName(material.getMaterialName());
inventory.setSpec(material.getSpec());
inventory.setModel(material.getModel());
inventory.setUnit(material.getUnit());
inventory.setCurrentStock(material.getCurrentStock());
}
List<MatPurchaseVo> purchases = purchaseMap.get(matId);
// 计算计划总数
BigDecimal planTotal = purchases.stream()
.map(MatPurchaseVo::getPlanNum)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
inventory.setPlanNum(planTotal);
// 计算已入库数量和在途数量
BigDecimal receivedTotal = BigDecimal.ZERO;
BigDecimal inTransitTotal = BigDecimal.ZERO;
for (MatPurchaseVo purchase : purchases) {
// 查询该采购单下的入库详情
MatPurchaseInDetailBo detailBo = new MatPurchaseInDetailBo();
detailBo.setPurchaseId(purchase.getPurchaseId());
List<MatPurchaseInDetailVo> details = matPurchaseInDetailService.queryList(detailBo);
// 计算该采购单的已入库数量
BigDecimal receivedInPurchase = details.stream()
.map(MatPurchaseInDetailVo::getInNum)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
receivedTotal = receivedTotal.add(receivedInPurchase);
// 根据采购单状态判断在途数量
// 1-待入(在途) 2-已完成(全部入库) 3-已取消(归零) 4-部分入库
if (purchase.getStatus() != null && (purchase.getStatus() == 1 || purchase.getStatus() == 4)) {
// 在途或部分入库,计算未入库数量
BigDecimal pending = purchase.getPlanNum().subtract(receivedInPurchase);
if (pending.compareTo(BigDecimal.ZERO) > 0) {
inTransitTotal = inTransitTotal.add(pending);
}
}
}
inventory.setReceivedNum(receivedTotal);
inventory.setInTransitNum(inTransitTotal);
result.add(inventory);
}
return result;
}
}

View File

@@ -1,31 +1,30 @@
package com.gear.mat.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.gear.common.utils.StringUtils;
import com.gear.common.core.page.TableDataInfo;
import com.gear.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.gear.mat.domain.bo.MatPurchaseInDetailBo;
import com.gear.mat.domain.vo.MatPurchaseInDetailVo;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gear.common.utils.StringUtils;
import com.gear.common.core.domain.PageQuery;
import com.gear.common.core.page.TableDataInfo;
import com.gear.mat.domain.MatMaterial;
import com.gear.mat.domain.MatPriceHistory;
import com.gear.mat.domain.MatPurchaseInDetail;
import com.gear.mat.domain.bo.*;
import com.gear.mat.domain.vo.*;
import com.gear.mat.mapper.MatMaterialMapper;
import com.gear.mat.mapper.MatPriceHistoryMapper;
import com.gear.mat.mapper.MatPurchaseInDetailMapper;
import com.gear.mat.service.IMatPurchaseInDetailService;
import com.gear.mat.service.IMatMaterialService;
import com.gear.mat.service.IMatPriceHistoryService;
import com.gear.mat.domain.vo.MatMaterialVo;
import com.gear.mat.domain.vo.MatPriceHistoryVo;
import com.gear.mat.domain.bo.MatMaterialBo;
import com.gear.mat.domain.bo.MatPriceHistoryBo;
import com.gear.mat.service.IMatPurchaseInDetailService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;
/**
@@ -39,7 +38,8 @@ import java.util.stream.Collectors;
public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailService {
private final MatPurchaseInDetailMapper baseMapper;
private final IMatMaterialService matMaterialService;
private final MatMaterialMapper matMaterialMapper; // 使用Mapper代替Service避免循环依赖
private final MatPriceHistoryMapper matPriceHistoryMapper; // 添加PriceHistory的Mapper
private final IMatPriceHistoryService matPriceHistoryService;
/**
@@ -190,16 +190,13 @@ public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailServi
* 更新库存数量
*/
private void updateInventory(Long materialId, BigDecimal quantity) {
MatMaterialVo material = matMaterialService.queryById(materialId);
MatMaterial material = matMaterialMapper.selectById(materialId);
if (material != null) {
MatMaterialBo materialBo = new MatMaterialBo();
materialBo.setMaterialId(materialId);
// 计算新的库存数量
BigDecimal newStock = material.getCurrentStock().add(quantity);
materialBo.setCurrentStock(newStock);
material.setCurrentStock(newStock);
matMaterialService.updateByBo(materialBo);
matMaterialMapper.updateById(material);
}
}
@@ -208,7 +205,7 @@ public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailServi
*/
private void insertPriceHistory(Long materialId, BigDecimal price, BigDecimal quantity, Long detailId) {
// 获取当前库存和平均价格信息
MatMaterialVo material = matMaterialService.queryById(materialId);
MatMaterial material = matMaterialMapper.selectById(materialId);
if (material != null) {
// 计算新的平均价格
BigDecimal avgPrice = calculateAveragePrice(materialId, price, quantity);
@@ -244,7 +241,7 @@ public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailServi
MatPriceHistoryVo lastHistory = historyList.get(0); // 最近的一条记录
// 获取当前物料的现有库存
MatMaterialVo material = matMaterialService.queryById(materialId);
MatMaterial material = matMaterialMapper.selectById(materialId);
if (material == null) {
return newPrice;
}
@@ -273,7 +270,7 @@ public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailServi
* 还原库存数量(减少库存)
*/
private void revertInventory(Long materialId, BigDecimal quantity) {
MatMaterialVo material = matMaterialService.queryById(materialId);
MatMaterial material = matMaterialMapper.selectById(materialId);
if (material != null) {
// 验证要减少的数量不能大于当前库存
if (quantity.compareTo(material.getCurrentStock()) > 0) {
@@ -281,17 +278,14 @@ public class MatPurchaseInDetailServiceImpl implements IMatPurchaseInDetailServi
}
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);
material.setCurrentStock(newStock);
matMaterialService.updateByBo(materialBo);
matMaterialMapper.updateById(material);
}
}

View File

@@ -20,10 +20,12 @@ import com.gear.mat.service.IMatPurchaseService;
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 java.util.*;
import java.math.BigDecimal;
import java.util.stream.Collectors;
import java.util.Objects;
/**
* 采购单主管理在途库存Service业务层处理
@@ -36,7 +38,7 @@ import java.util.stream.Collectors;
public class MatPurchaseServiceImpl implements IMatPurchaseService {
private final MatPurchaseMapper baseMapper;
private final IMatMaterialService matMaterialService;
private final MatMaterialMapper matMaterialMapper;
private final IMatPurchaseInDetailService matPurchaseInDetailService;
/**
@@ -145,98 +147,51 @@ public class MatPurchaseServiceImpl implements IMatPurchaseService {
return baseMapper.deleteBatchIds(ids) > 0;
}
@Override
public List<MaterialInventoryVo> queryMaterialInventory(Long materialId) {
// 创建结果列表
List<MaterialInventoryVo> result = new ArrayList<>();
// 构建查询条件
MatPurchaseBo purchaseBo = new MatPurchaseBo();
if (materialId != null) {
purchaseBo.setMaterialId(materialId);
}
// 查询所有相关的采购单
List<MatPurchaseVo> purchaseList = queryList(purchaseBo);
// 按物料ID分组
Map<Long, List<MatPurchaseVo>> purchaseMap = purchaseList.stream()
.collect(Collectors.groupingBy(MatPurchaseVo::getMaterialId));
for (Long matId : purchaseMap.keySet()) {
// 获取物料信息
MatMaterialVo material = matMaterialService.queryById(matId);
MaterialInventoryVo inventory = new MaterialInventoryVo();
inventory.setMaterialId(matId);
if (material != null) {
inventory.setMaterialName(material.getMaterialName());
inventory.setSpec(material.getSpec());
inventory.setModel(material.getModel());
inventory.setUnit(material.getUnit());
inventory.setCurrentStock(material.getCurrentStock());
}
List<MatPurchaseVo> purchases = purchaseMap.get(matId);
// 计算计划总数
BigDecimal planTotal = purchases.stream()
.map(MatPurchaseVo::getPlanNum)
.filter(num -> num != null)
.reduce(BigDecimal.ZERO, BigDecimal::add);
inventory.setPlanNum(planTotal);
// 计算已入库数量和在途数量
BigDecimal receivedTotal = BigDecimal.ZERO;
BigDecimal inTransitTotal = BigDecimal.ZERO;
for (MatPurchaseVo purchase : purchases) {
// 查询该采购单下的入库详情
MatPurchaseInDetailBo detailBo = new MatPurchaseInDetailBo();
detailBo.setPurchaseId(purchase.getPurchaseId());
List<MatPurchaseInDetailVo> details = matPurchaseInDetailService.queryList(detailBo);
// 计算该采购单的已入库数量
BigDecimal receivedInPurchase = details.stream()
.map(MatPurchaseInDetailVo::getInNum)
.filter(num -> num != null)
.reduce(BigDecimal.ZERO, BigDecimal::add);
receivedTotal = receivedTotal.add(receivedInPurchase);
// 根据采购单状态判断在途数量
// 1-待入(在途) 2-已完成(全部入库) 3-已取消(归零) 4-部分入库
if (purchase.getStatus() != null && (purchase.getStatus() == 1 || purchase.getStatus() == 4)) {
// 在途或部分入库,计算未入库数量
BigDecimal pending = purchase.getPlanNum().subtract(receivedInPurchase);
if (pending.compareTo(BigDecimal.ZERO) > 0) {
inTransitTotal = inTransitTotal.add(pending);
}
}
}
inventory.setReceivedNum(receivedTotal);
inventory.setInTransitNum(inTransitTotal);
result.add(inventory);
}
return result;
}
public List<MatPurchaseVo> convertToPurchaseWithInventoryVo(List<MatPurchaseVo> purchaseList) {
List<MatPurchaseVo> result = new ArrayList<>();
// 批量获取所有物料ID
Set<Long> materialIds = purchaseList.stream()
.map(MatPurchaseVo::getMaterialId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询物料信息避免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);
}
}
}
// 批量获取所有采购单ID
List<Long> purchaseIds = purchaseList.stream()
.map(MatPurchaseVo::getPurchaseId)
.collect(Collectors.toList());
// 批量查询所有入库详情避免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);
}
}
for (MatPurchaseVo purchase : purchaseList) {
MatPurchaseVo vo = new MatPurchaseVo();
// 复制基本采购信息
BeanUtils.copyProperties(purchase, vo);
// 获取物料信息
MatMaterialVo material = matMaterialService.queryById(purchase.getMaterialId());
// 获取物料信息 - 从预加载的Map中获取
MatMaterialVo material = materialMap.get(purchase.getMaterialId());
if (material != null) {
vo.setMaterialName(material.getMaterialName());
vo.setSpec(material.getSpec());
@@ -245,15 +200,17 @@ public class MatPurchaseServiceImpl implements IMatPurchaseService {
vo.setCurrentStock(material.getCurrentStock());
}
// 计算已入库数量
MatPurchaseInDetailBo detailBo = new MatPurchaseInDetailBo();
detailBo.setPurchaseId(purchase.getPurchaseId());
List<MatPurchaseInDetailVo> details = matPurchaseInDetailService.queryList(detailBo);
// 获取入库详情 - 从预加载的Map中获取
List<MatPurchaseInDetailVo> details = detailMap.get(purchase.getPurchaseId());
BigDecimal receivedTotal = details.stream()
// 计算已入库数量
BigDecimal receivedTotal = BigDecimal.ZERO;
if (details != null && !details.isEmpty()) {
receivedTotal = details.stream()
.map(MatPurchaseInDetailVo::getInNum)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
vo.setReceivedNum(receivedTotal);
@@ -278,71 +235,4 @@ public class MatPurchaseServiceImpl implements IMatPurchaseService {
return result;
}
@Override
public List<PurchasePlanMaterialInventoryVo> queryPurchasePlanMaterialInventory(Long purchaseId) {
List<PurchasePlanMaterialInventoryVo> result = new ArrayList<>();
if (purchaseId == null) {
return result;
}
// 查询采购单信息
MatPurchaseVo purchase = queryById(purchaseId);
if (purchase == null) {
return result;
}
// 获取物料信息
MatMaterialVo material = matMaterialService.queryById(purchase.getMaterialId());
// 创建库存信息对象
PurchasePlanMaterialInventoryVo inventory = new PurchasePlanMaterialInventoryVo();
inventory.setPurchaseId(purchase.getPurchaseId());
inventory.setPurchaseNo(purchase.getPurchaseNo());
inventory.setMaterialId(purchase.getMaterialId());
if (material != null) {
inventory.setMaterialName(material.getMaterialName());
inventory.setSpec(material.getSpec());
inventory.setModel(material.getModel());
inventory.setUnit(material.getUnit());
inventory.setCurrentStock(material.getCurrentStock());
}
// 设置计划采购数量
inventory.setPlanNum(purchase.getPlanNum());
// 查询该采购单下的入库详情
MatPurchaseInDetailBo detailBo = new MatPurchaseInDetailBo();
detailBo.setPurchaseId(purchase.getPurchaseId());
List<MatPurchaseInDetailVo> details = matPurchaseInDetailService.queryList(detailBo);
// 计算已入库数量
BigDecimal receivedTotal = details.stream()
.map(MatPurchaseInDetailVo::getInNum)
.filter(num -> num != null)
.reduce(BigDecimal.ZERO, BigDecimal::add);
inventory.setReceivedNum(receivedTotal);
// 根据采购单状态判断在途数量
// 1-待入(在途) 2-已完成(全部入库) 3-已取消(归零) 4-部分入库
if (purchase.getStatus() != null && (purchase.getStatus() == 1 || purchase.getStatus() == 4)) {
// 在途或部分入库,计算未入库数量
BigDecimal pending = purchase.getPlanNum().subtract(receivedTotal);
if (pending.compareTo(BigDecimal.ZERO) > 0) {
inventory.setInTransitNum(pending);
} else {
inventory.setInTransitNum(BigDecimal.ZERO);
}
} else {
// 已完成或已取消,没有在途数量
inventory.setInTransitNum(BigDecimal.ZERO);
}
result.add(inventory);
return result;
}
}