Files
klp-oa/klp-wms/src/main/java/com/klp/service/impl/WmsStockServiceImpl.java
Joshi 51506bbb66 feat(stock): 优化实际库区查询速率使用CTE递归查询
- 修改 WmsStockMapper 接口,增加 rootWarehouseId 参数用于限定查询范围
- 优化 WmsStockMapper.xml 中的 SQL 查询逻辑,引入 CTE 递归查询子仓库数据
- 调整查询语句结构,将分组统计与关联查询分离以提升性能
- 移除 Java 层递归获取子仓库 ID 的逻辑,改为数据库端处理
- 强制使用指定索引 idx_mc_fixed_group 提高查询效率
- 更新服务实现类传参逻辑,传递实际仓库 ID 用于构建查询条件
2025-12-18 09:41:22 +08:00

253 lines
9.6 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.klp.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.klp.common.core.page.TableDataInfo;
import com.klp.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.klp.common.utils.StringUtils;
import com.klp.domain.WmsActualWarehouse;
import com.klp.mapper.WmsActualWarehouseMapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import com.klp.domain.bo.WmsStockBo;
import com.klp.domain.vo.WmsStockVo;
import com.klp.domain.WmsStock;
import com.klp.domain.WmsWarehouse;
import com.klp.mapper.WmsStockMapper;
import com.klp.mapper.WmsWarehouseMapper;
import com.klp.service.IWmsStockService;
import java.math.BigDecimal;
import java.util.*;
/**
* 库存:原材料/产品与库区/库位的存放关系Service业务层处理
*
* @author Joshi
* @date 2025-07-18
*/
@RequiredArgsConstructor
@Service
public class WmsStockServiceImpl implements IWmsStockService {
private final WmsStockMapper baseMapper;
private final WmsWarehouseMapper warehouseMapper;
private final WmsActualWarehouseMapper actualWarehouseMapper;
/**
* 查询库存:原材料/产品与库区/库位的存放关系
*/
@Override
public WmsStockVo queryById(Long stockId){
return baseMapper.selectVoById(stockId);
}
/**
* 查询库存:原材料/产品与库区/库位的存放关系列表
*/
@Override
public TableDataInfo<WmsStockVo> queryPageList(WmsStockBo bo, PageQuery pageQuery) {
QueryWrapper<WmsStock> lqw = buildQueryWrapperPlus(bo);
Page<WmsStockVo> result = baseMapper.selectVoPagePlus(pageQuery.build(), lqw);
return TableDataInfo.build(result);
}
@Override
public TableDataInfo<WmsStockVo> queryPageListActual(WmsStockBo bo, PageQuery pageQuery) {
QueryWrapper<WmsStock> lqw = buildQueryWrapperPlusActual(bo);
Page<WmsStockVo> result = baseMapper.selectVoPagePlusActual(pageQuery.build(), lqw, bo.getActualWarehouseId());
return TableDataInfo.build(result);
}
private QueryWrapper<WmsStock> buildQueryWrapperPlusActual(WmsStockBo bo) { // 注意:这里改用 QueryWrapper 而非 LambdaQueryWrapper
QueryWrapper<WmsStock> qw = Wrappers.query();
// 固定条件:未删除的现存数据
qw.eq("mc.del_flag", 0);
qw.eq("mc.data_type", 1);
qw.eq("mc.status", 0);
qw.in("mc.material_type", Arrays.asList("成品", "原料"));
// 递归库区过滤下放到 SQL (CTE) 中处理,避免 Java 侧递归造成超大 IN 列表
qw.eq(StringUtils.isNotBlank(bo.getItemType()), "mc.item_type", bo.getItemType());
qw.eq(bo.getItemId() != null, "mc.item_id", bo.getItemId());
// 优化模糊查询为前缀匹配,便于走索引
qw.likeRight(StringUtils.isNotBlank(bo.getBatchNo()), "mc.enter_coil_no", bo.getBatchNo());
// 分组改由 SQL 中显式控制,避免在 Wrapper 中拼装大量分组字段
return qw;
}
/**
* 查询库存:原材料/产品与库区/库位的存放关系列表
*/
@Override
public List<WmsStockVo> queryList(WmsStockBo bo) {
LambdaQueryWrapper<WmsStock> lqw = buildQueryWrapper(bo);
return baseMapper.selectVoList(lqw);
}
private LambdaQueryWrapper<WmsStock> buildQueryWrapper(WmsStockBo bo) {
LambdaQueryWrapper<WmsStock> lqw = new LambdaQueryWrapper<>();
lqw.eq(StringUtils.isNotBlank(bo.getItemType()), WmsStock::getItemType, bo.getItemType());
lqw.eq(bo.getItemId() != null, WmsStock::getItemId, bo.getItemId());
lqw.eq(StringUtils.isNotBlank(bo.getBatchNo()), WmsStock::getBatchNo, bo.getBatchNo());
return lqw;
}
private QueryWrapper<WmsStock> buildQueryWrapperPlus(WmsStockBo bo) { // 注意:这里改用 QueryWrapper 而非 LambdaQueryWrapper
QueryWrapper<WmsStock> qw = Wrappers.query(); // 使用普通 QueryWrapper
// 固定条件:未删除的现存数据
qw.eq("mc.del_flag", 0);
qw.eq("mc.data_type", 1);
// 处理仓库ID查询支持递归查询子节点
if (bo.getWarehouseId() != null) {
List<Long> warehouseIds = getWarehouseIdsWithChildren(bo.getWarehouseId());
if (warehouseIds.size() == 1) {
qw.eq("mc.warehouse_id", warehouseIds.get(0));
} else {
qw.in("mc.warehouse_id", warehouseIds);
}
}
qw.eq(StringUtils.isNotBlank(bo.getItemType()), "mc.item_type", bo.getItemType());
qw.eq(bo.getItemId() != null, "mc.item_id", bo.getItemId());
qw.like(StringUtils.isNotBlank(bo.getBatchNo()), "mc.enter_coil_no", bo.getBatchNo());
// 使用 groupBy 进行分组
qw.groupBy(
"mc.warehouse_id",
"mc.item_type",
"mc.item_id",
"w.warehouse_name",
"CASE WHEN mc.item_type = 'product' THEN p.product_name WHEN mc.item_type = 'raw_material' THEN r.raw_material_name ELSE NULL END",
"CASE WHEN mc.item_type = 'product' THEN p.product_code WHEN mc.item_type = 'raw_material' THEN r.raw_material_code ELSE NULL END",
// 新增分组字段(规格、材质等)
"CASE WHEN mc.item_type = 'product' THEN p.specification WHEN mc.item_type = 'raw_material' THEN r.specification ELSE NULL END",
"CASE WHEN mc.item_type = 'product' THEN p.material WHEN mc.item_type = 'raw_material' THEN r.material ELSE NULL END",
"CASE WHEN mc.item_type = 'product' THEN p.surface_treatment_desc WHEN mc.item_type = 'raw_material' THEN r.surface_treatment_desc ELSE NULL END",
"CASE WHEN mc.item_type = 'product' THEN p.zinc_layer WHEN mc.item_type = 'raw_material' THEN r.zinc_layer ELSE NULL END",
"CASE WHEN mc.item_type = 'product' THEN p.manufacturer WHEN mc.item_type = 'raw_material' THEN r.manufacturer ELSE NULL END"
);
return qw;
}
/**
* 新增库存:原材料/产品与库区/库位的存放关系
*/
@Override
public Boolean insertByBo(WmsStockBo bo) {
WmsStock add = BeanUtil.toBean(bo, WmsStock.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setStockId(add.getStockId());
}
return flag;
}
/**
* 修改库存:原材料/产品与库区/库位的存放关系
*/
@Override
public Boolean updateByBo(WmsStockBo bo) {
WmsStock update = BeanUtil.toBean(bo, WmsStock.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(WmsStock entity){
//TODO 做一些数据校验,如唯一约束
}
/**
* 批量删除库存:原材料/产品与库区/库位的存放关系
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
@Override
public BigDecimal getStockByItemId(Long rawMaterialId) {
return baseMapper.getStockByItemId(rawMaterialId);
}
/**
* 查询库存分布(按仓库统计)
*/
@Override
public WmsStockVo queryStockDistribution(WmsStockBo bo) {
if (bo.getItemType() == null || bo.getItemId() == null) {
throw new RuntimeException("物品类型和物品ID不能为空");
}
// 1. 计算总库存量
BigDecimal totalQuantity = BigDecimal.ZERO;
// 2. 按仓库统计库存分布
List<WmsStockVo> distributionList = baseMapper.selectStockDistribution(bo.getItemType(), bo.getItemId());
// 计算总量
for (WmsStockVo vo : distributionList) {
if (vo.getTotalQuantity() != null) {
totalQuantity = totalQuantity.add(vo.getTotalQuantity());
}
}
// 构建返回对象
WmsStockVo result = new WmsStockVo();
result.setItemType(bo.getItemType());
result.setItemId(bo.getItemId());
result.setTotalQuantity(totalQuantity);
result.setStockDistribution(distributionList);
return result;
}
/**
* 获取指定仓库ID及其所有子仓库ID
* @param warehouseId 仓库ID
* @return 包含当前仓库ID和所有子仓库ID的列表
*/
private List<Long> getWarehouseIdsWithChildren(Long warehouseId) {
List<Long> warehouseIds = new ArrayList<>();
warehouseIds.add(warehouseId); // 添加当前仓库ID
// 递归获取所有子仓库ID
getChildWarehouseIds(warehouseId, warehouseIds);
return warehouseIds;
}
/**
* 递归获取子仓库ID
* @param parentId 父仓库ID
* @param warehouseIds 仓库ID列表用于收集结果
*/
private void getChildWarehouseIds(Long parentId, List<Long> warehouseIds) {
// 查询直接子仓库
List<WmsWarehouse> children = warehouseMapper.selectList(
Wrappers.<WmsWarehouse>lambdaQuery()
.eq(WmsWarehouse::getParentId, parentId)
.eq(WmsWarehouse::getDelFlag, 0)
);
// 递归处理每个子仓库
for (WmsWarehouse child : children) {
warehouseIds.add(child.getWarehouseId());
getChildWarehouseIds(child.getWarehouseId(), warehouseIds);
}
}
}