Files
klp-oa/klp-wms/src/main/java/com/klp/service/impl/WmsActualWarehouseServiceImpl.java
Joshi bfba3ce49a feat(warehouse): 添加库位排序号修正功能并优化钢卷查询排序
- 在 IWmsActualWarehouseService 中新增 fixSortNoByParentId 方法用于修正库位排序号
- 在 WmsActualWarehouseController 中新增 /fixSortNo/{parentId} 接口
- 实现 WmsActualWarehouseServiceImpl 中的 fixSortNoByParentId 业务逻辑
- 添加正则表达式处理库位编码的排序键解析
- 新增 WmsMaterialCoilMapper 的 selectVoPagePlusOrderBy 查询方法
- 重构 WmsMaterialCoilMapper.xml 中的排序辅助字段查询逻辑
- 修改 WmsMaterialCoilServiceImpl 中的分页查询逻辑以支持排序功能
2026-03-12 17:05:22 +08:00

918 lines
40 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 cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.klp.common.exception.ServiceException;
import com.klp.common.utils.StringUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.klp.domain.bo.WmsActualWarehouseBo;
import com.klp.domain.bo.WmsActualWarehouseHierarchyBo;
import com.klp.domain.bo.WmsActualWarehouseBatchGenerateBo;
import com.klp.domain.bo.WmsActualWarehouseSplitBo;
import com.klp.domain.vo.WmsActualWarehouseTreeVo;
import com.klp.domain.vo.WmsActualWarehouseImportVo;
import com.klp.domain.vo.WmsActualWarehouseVo;
import com.klp.domain.WmsActualWarehouse;
import com.klp.mapper.WmsActualWarehouseMapper;
import com.klp.service.IWmsActualWarehouseService;
import com.klp.domain.WmsMaterialCoil;
import com.klp.mapper.WmsMaterialCoilMapper;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* 实际库区/库位自关联Service业务层处理
*
* @author klp
* @date 2025-11-24
*/
@RequiredArgsConstructor
@Service
public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService {
private final WmsActualWarehouseMapper baseMapper;
private final WmsMaterialCoilMapper wmsMaterialCoilMapper;
/**
* 查询实际库区/库位自关联
*/
@Override
public WmsActualWarehouseVo queryById(Long actualWarehouseId) {
return baseMapper.selectVoById(actualWarehouseId);
}
/**
* 释放实际库区/库位:
* 1) 将该库区设置为未被占用isEnabled=1
* 2) 清空钢卷表中绑定此库区且为现存(data_type=1)、未删除(del_flag=0)的记录的 actual_warehouse_id
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void releaseActualWarehouse(Long actualWarehouseId) {
if (actualWarehouseId == null || actualWarehouseId <= 0) {
throw new ServiceException("参数actualWarehouseId不合法");
}
// 1) 设置库区为启用(未被占用)
WmsActualWarehouse update = new WmsActualWarehouse();
update.setActualWarehouseId(actualWarehouseId);
update.setIsEnabled(1);
int affected = baseMapper.updateById(update);
if (affected <= 0) {
// 不存在也提示
throw new ServiceException("实际库区不存在或更新失败: " + actualWarehouseId);
}
// 2) 清空钢卷绑定(仅现存且未删除)- 使用UpdateWrapper进行更新
UpdateWrapper<WmsMaterialCoil> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("actual_warehouse_id", actualWarehouseId)
.eq("data_type", 1)
.eq("del_flag", 0)
.set("actual_warehouse_id", null);
wmsMaterialCoilMapper.update(null, updateWrapper);
}
@Override
@Transactional(rollbackFor = Exception.class)
public int batchGenerateLocations(WmsActualWarehouseBatchGenerateBo bo) {
if (bo == null) {
throw new ServiceException("参数不能为空");
}
Integer rows = bo.getRowCount();
Integer cols = bo.getColumnCount();
Integer layers = bo.getLayerCount();
String prefix = bo.getPrefix();
Long parentId = bo.getParentId();
if (layers == null) {
layers = 2;
}
if (rows == null || cols == null || rows < 1 || cols < 1) {
throw new ServiceException("行/列必须为正整数");
}
if (layers < 1) {
throw new ServiceException("层必须为正整数");
}
if (StringUtils.isBlank(prefix)) {
throw new ServiceException("前缀不能为空");
}
List<String> codes = new ArrayList<>(rows * cols * layers);
for (int l = 1; l <= layers; l++) {
int rowsForLayer = (l == 2) ? Math.max(rows - 1, 1) : rows;
for (int c = 1; c <= cols; c++) {
// 列直接显示数字1→110→10无需补0
String cStr = String.valueOf(c);
for (int r = 1; r <= rowsForLayer; r++) {
// 行的逻辑1→0110→10
String rStr = r < 10 ? ("0" + r) : String.valueOf(r);
// 拼接编码:前缀 + 列 + - + 行 + - + 层
String code = prefix + cStr + '-' + rStr + '-' + l;
codes.add(code);
}
}
}
// 去重:过滤同父级下已存在的编码
List<WmsActualWarehouse> exists = baseMapper.selectList(Wrappers.<WmsActualWarehouse>lambdaQuery()
.eq(WmsActualWarehouse::getParentId, parentId)
.in(!codes.isEmpty(), WmsActualWarehouse::getActualWarehouseCode, codes));
Set<String> existCodes = exists.stream().map(WmsActualWarehouse::getActualWarehouseCode).collect(Collectors.toSet());
List<WmsActualWarehouse> toInsert = new ArrayList<>();
for (String code : codes) {
if (existCodes.contains(code)) {
continue;
}
WmsActualWarehouse e = new WmsActualWarehouse();
e.setParentId(parentId);
e.setActualWarehouseType(3L);
e.setActualWarehouseCode(code);
e.setActualWarehouseName(code);
e.setSortNo(0L);
e.setIsEnabled(1);
toInsert.add(e);
}
if (toInsert.isEmpty()) {
return 0;
}
boolean ok = baseMapper.insertBatch(toInsert);
if (!ok) {
throw new ServiceException("批量生成失败");
}
return toInsert.size();
}
/**
* 查询实际库区/库位自关联列表
*/
@Override
public List<WmsActualWarehouseVo> queryList(WmsActualWarehouseBo bo) {
LambdaQueryWrapper<WmsActualWarehouse> lqw = buildQueryWrapper(bo);
List<WmsActualWarehouseVo> list = baseMapper.selectVoList(lqw);
if (CollUtil.isEmpty(list)) {
return list;
}
// 如果当前查询的是某二级父节点下的三级库位,需要将被拆分的大库位替换为其两个子库位
if (bo.getParentId() != null) {
List<Long> splitParentIds = list.stream()
.filter(v -> Optional.ofNullable(v.getSplitStatus()).orElse(0) == 1)
.map(WmsActualWarehouseVo::getActualWarehouseId)
.collect(Collectors.toList());
if (CollUtil.isNotEmpty(splitParentIds)) {
List<WmsActualWarehouseVo> children = baseMapper.selectVoList(
Wrappers.<WmsActualWarehouse>lambdaQuery()
.in(WmsActualWarehouse::getParentId, splitParentIds)
.eq(WmsActualWarehouse::getDelFlag, 0)
.orderByAsc(WmsActualWarehouse::getActualWarehouseCode, WmsActualWarehouse::getActualWarehouseId)
);
Map<Long, List<WmsActualWarehouseVo>> childrenMap = children.stream()
.collect(Collectors.groupingBy(WmsActualWarehouseVo::getParentId, LinkedHashMap::new, Collectors.toList()));
List<WmsActualWarehouseVo> expanded = new ArrayList<>(list.size() + children.size());
for (WmsActualWarehouseVo v : list) {
if (Optional.ofNullable(v.getSplitStatus()).orElse(0) == 1) {
List<WmsActualWarehouseVo> ch = childrenMap.get(v.getActualWarehouseId());
if (CollUtil.isNotEmpty(ch)) {
expanded.addAll(ch);
continue;
}
}
expanded.add(v);
}
list = expanded;
}
}
// 仅对被占用的位置isEnabled=0补充钢卷信息
List<Long> occupiedAwIds = list.stream()
.filter(it -> it.getIsEnabled() != null && it.getIsEnabled() == 0)
.map(WmsActualWarehouseVo::getActualWarehouseId)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (CollUtil.isEmpty(occupiedAwIds)) {
return list;
}
// 查询当前在库(del_flag=0, status=0)并且占用这些库位的钢卷,仅选择必要列
List<WmsMaterialCoil> coils = wmsMaterialCoilMapper.selectList(
Wrappers.<WmsMaterialCoil>lambdaQuery()
.select(
WmsMaterialCoil::getActualWarehouseId,
WmsMaterialCoil::getCoilId,
WmsMaterialCoil::getEnterCoilNo,
WmsMaterialCoil::getCurrentCoilNo,
WmsMaterialCoil::getSupplierCoilNo
)
.in(WmsMaterialCoil::getActualWarehouseId, occupiedAwIds)
.eq(WmsMaterialCoil::getDataType, 1)
.eq(WmsMaterialCoil::getDelFlag, 0)
.eq(WmsMaterialCoil::getStatus, 0)
);
if (CollUtil.isEmpty(coils)) {
return list;
}
// 同一库位若有多条记录,选择最新一条(按更新顺序无字段,这里按主键最大)
Map<Long, WmsMaterialCoil> coilByAwId = coils.stream()
.collect(Collectors.toMap(
WmsMaterialCoil::getActualWarehouseId,
c -> c,
(a, b) -> a.getCoilId() != null && b.getCoilId() != null && a.getCoilId() > b.getCoilId() ? a : b
));
list.forEach(it -> {
if (it.getIsEnabled() != null && it.getIsEnabled() == 0) {
WmsMaterialCoil c = coilByAwId.get(it.getActualWarehouseId());
if (c != null) {
it.setEnterCoilNo(c.getEnterCoilNo());
it.setCurrentCoilNo(c.getCurrentCoilNo());
it.setSupplierCoilNo(c.getSupplierCoilNo());
}
}
});
return list;
}
@Override
public List<WmsActualWarehouseTreeVo> queryTree(WmsActualWarehouseBo bo) {
if (bo == null) {
bo = new WmsActualWarehouseBo();
}
LambdaQueryWrapper<WmsActualWarehouse> wrapper = Wrappers.lambdaQuery();
wrapper.eq(StringUtils.isNotBlank(bo.getActualWarehouseCode()), WmsActualWarehouse::getActualWarehouseCode, bo.getActualWarehouseCode());
wrapper.like(StringUtils.isNotBlank(bo.getActualWarehouseName()), WmsActualWarehouse::getActualWarehouseName, bo.getActualWarehouseName());
wrapper.eq(bo.getActualWarehouseType() != null, WmsActualWarehouse::getActualWarehouseType, bo.getActualWarehouseType());
wrapper.eq(bo.getIsEnabled() != null, WmsActualWarehouse::getIsEnabled, bo.getIsEnabled());
wrapper.orderByAsc(WmsActualWarehouse::getSortNo, WmsActualWarehouse::getActualWarehouseId);
List<WmsActualWarehouseVo> flatList = baseMapper.selectVoList(wrapper);
Map<Long, WmsActualWarehouseTreeVo> nodeMap = new LinkedHashMap<>();
flatList.forEach(item -> {
WmsActualWarehouseTreeVo node = new WmsActualWarehouseTreeVo();
BeanUtil.copyProperties(item, node);
nodeMap.put(node.getActualWarehouseId(), node);
});
List<WmsActualWarehouseTreeVo> roots = new ArrayList<>();
nodeMap.values().forEach(node -> {
Long parentId = Optional.ofNullable(node.getParentId()).orElse(0L);
if (parentId == 0 || !nodeMap.containsKey(parentId)) {
roots.add(node);
} else {
nodeMap.get(parentId).getChildren().add(node);
}
});
return roots;
}
@Override
public List<WmsActualWarehouseTreeVo> queryTreeExcludeLevelThree(WmsActualWarehouseBo bo) {
if (bo == null) {
bo = new WmsActualWarehouseBo();
}
// 设置查询条件排除三级节点actualWarehouseType=3
LambdaQueryWrapper<WmsActualWarehouse> wrapper = Wrappers.lambdaQuery();
wrapper.eq(StringUtils.isNotBlank(bo.getActualWarehouseCode()), WmsActualWarehouse::getActualWarehouseCode, bo.getActualWarehouseCode());
wrapper.like(StringUtils.isNotBlank(bo.getActualWarehouseName()), WmsActualWarehouse::getActualWarehouseName, bo.getActualWarehouseName());
wrapper.eq(bo.getActualWarehouseType() != null, WmsActualWarehouse::getActualWarehouseType, bo.getActualWarehouseType());
wrapper.eq(bo.getIsEnabled() != null, WmsActualWarehouse::getIsEnabled, bo.getIsEnabled());
// 排除三级节点类型为3的节点
wrapper.ne(WmsActualWarehouse::getActualWarehouseType, 3);
// 四级节点也排除
wrapper.ne(WmsActualWarehouse::getActualWarehouseType, 4);
wrapper.orderByAsc(WmsActualWarehouse::getSortNo, WmsActualWarehouse::getActualWarehouseId);
List<WmsActualWarehouseVo> flatList = baseMapper.selectVoList(wrapper);
Map<Long, WmsActualWarehouseTreeVo> nodeMap = new LinkedHashMap<>();
flatList.forEach(item -> {
WmsActualWarehouseTreeVo node = new WmsActualWarehouseTreeVo();
BeanUtil.copyProperties(item, node);
nodeMap.put(node.getActualWarehouseId(), node);
});
List<WmsActualWarehouseTreeVo> roots = new ArrayList<>();
nodeMap.values().forEach(node -> {
Long parentId = Optional.ofNullable(node.getParentId()).orElse(0L);
if (parentId == 0 || !nodeMap.containsKey(parentId)) {
roots.add(node);
} else {
nodeMap.get(parentId).getChildren().add(node);
}
});
return roots;
}
private LambdaQueryWrapper<WmsActualWarehouse> buildQueryWrapper(WmsActualWarehouseBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<WmsActualWarehouse> lqw = Wrappers.lambdaQuery();
lqw.eq(bo.getParentId() != null, WmsActualWarehouse::getParentId, bo.getParentId());
lqw.eq(StringUtils.isNotBlank(bo.getActualWarehouseCode()), WmsActualWarehouse::getActualWarehouseCode, bo.getActualWarehouseCode());
lqw.like(StringUtils.isNotBlank(bo.getActualWarehouseName()), WmsActualWarehouse::getActualWarehouseName, bo.getActualWarehouseName());
lqw.eq(bo.getActualWarehouseType() != null, WmsActualWarehouse::getActualWarehouseType, bo.getActualWarehouseType());
lqw.eq(bo.getSortNo() != null, WmsActualWarehouse::getSortNo, bo.getSortNo());
lqw.eq(bo.getIsEnabled() != null, WmsActualWarehouse::getIsEnabled, bo.getIsEnabled());
return lqw;
}
/**
* 新增实际库区/库位自关联
*/
@Override
public Boolean insertByBo(WmsActualWarehouseBo bo) {
WmsActualWarehouse add = BeanUtil.toBean(bo, WmsActualWarehouse.class);
validEntityBeforeSave(add);
boolean flag = baseMapper.insert(add) > 0;
if (flag) {
bo.setActualWarehouseId(add.getActualWarehouseId());
}
return flag;
}
/**
* 修改实际库区/库位自关联
*/
@Override
public Boolean updateByBo(WmsActualWarehouseBo bo) {
WmsActualWarehouse update = BeanUtil.toBean(bo, WmsActualWarehouse.class);
validEntityBeforeSave(update);
return baseMapper.updateById(update) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public List<Long> createHierarchy(WmsActualWarehouseHierarchyBo bo) {
if (bo.getLevels() == null || bo.getLevels().isEmpty()) {
throw new ServiceException("层级数据不能为空");
}
List<WmsActualWarehouseHierarchyBo.HierarchyLevel> sortedLevels = bo.getLevels().stream()
.sorted(Comparator.comparing(WmsActualWarehouseHierarchyBo.HierarchyLevel::getLevel))
.collect(Collectors.toList());
Long parentId = 0L;
List<Long> createdIds = new ArrayList<>();
for (WmsActualWarehouseHierarchyBo.HierarchyLevel level : sortedLevels) {
Integer levelValue = level.getLevel();
if (levelValue == null || levelValue < 1 || levelValue > 3) {
throw new ServiceException("层级必须在1-3之间");
}
if (level.getActualWarehouseId() != null) {
WmsActualWarehouse existing = baseMapper.selectById(level.getActualWarehouseId());
if (existing == null) {
throw new ServiceException("指定的节点不存在:" + level.getActualWarehouseId());
}
if (!Objects.equals(Optional.ofNullable(existing.getParentId()).orElse(0L), parentId)) {
throw new ServiceException("节点不属于当前父级,无法复用");
}
parentId = existing.getActualWarehouseId();
continue;
}
if (StringUtils.isBlank(level.getActualWarehouseCode()) || StringUtils.isBlank(level.getActualWarehouseName())) {
throw new ServiceException("编码与名称不能为空");
}
WmsActualWarehouse duplicate = baseMapper.selectOne(Wrappers.<WmsActualWarehouse>lambdaQuery()
.eq(WmsActualWarehouse::getParentId, parentId)
.and(wrapper -> wrapper.eq(WmsActualWarehouse::getActualWarehouseCode, level.getActualWarehouseCode())
.or()
.eq(WmsActualWarehouse::getActualWarehouseName, level.getActualWarehouseName())));
if (duplicate != null) {
parentId = duplicate.getActualWarehouseId();
continue;
}
WmsActualWarehouse entity = new WmsActualWarehouse();
entity.setParentId(parentId);
entity.setActualWarehouseType(levelValue.longValue());
entity.setActualWarehouseCode(level.getActualWarehouseCode());
entity.setActualWarehouseName(level.getActualWarehouseName());
entity.setSortNo(Optional.ofNullable(level.getSortNo()).orElse(0L));
entity.setIsEnabled(Optional.ofNullable(level.getIsEnabled()).orElse(1));
entity.setRemark(level.getRemark());
baseMapper.insert(entity);
parentId = entity.getActualWarehouseId();
createdIds.add(parentId);
}
return createdIds;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void importHierarchy(List<WmsActualWarehouseImportVo> importList) {
if (CollUtil.isEmpty(importList)) {
throw new ServiceException("导入数据不能为空");
}
for (WmsActualWarehouseImportVo row : importList) {
List<WmsActualWarehouseHierarchyBo.HierarchyLevel> levels = new ArrayList<>();
appendLevel(levels, 1, row.getLevelOneCode(), row.getLevelOneName(), row.getLevelOneSort());
appendLevel(levels, 2, row.getLevelTwoCode(), row.getLevelTwoName(), row.getLevelTwoSort());
appendLevel(levels, 3, row.getLevelThreeCode(), row.getLevelThreeName(), row.getLevelThreeSort());
if (levels.isEmpty()) {
continue;
}
WmsActualWarehouseHierarchyBo bo = new WmsActualWarehouseHierarchyBo();
bo.setLevels(levels);
createHierarchy(bo);
}
}
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(WmsActualWarehouse entity) {
//TODO 做一些数据校验,如唯一约束
}
private void appendLevel(List<WmsActualWarehouseHierarchyBo.HierarchyLevel> levels,
int level,
String code,
String name,
Long sortNo) {
if (StringUtils.isBlank(code) || StringUtils.isBlank(name)) {
return;
}
WmsActualWarehouseHierarchyBo.HierarchyLevel item = new WmsActualWarehouseHierarchyBo.HierarchyLevel();
item.setLevel(level);
item.setActualWarehouseCode(code.trim());
item.setActualWarehouseName(name.trim());
item.setSortNo(Optional.ofNullable(sortNo).orElse(0L));
item.setIsEnabled(1);
levels.add(item);
}
/**
* 批量删除实际库区/库位自关联
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if (isValid) {
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public void splitLocations(WmsActualWarehouseSplitBo bo) {
if (bo == null) {
throw new ServiceException("参数不能为空");
}
// 参数验证
String columnFlag = bo.getColumnFlag();
if (StringUtils.isBlank(columnFlag)) {
throw new ServiceException("列标识不能为空");
}
int splitType = Optional.ofNullable(bo.getSplitType()).orElse(0);
List<Long> Ids = Optional.ofNullable(bo.getSplitIds()).orElse(new ArrayList<>());
// 合并所有待拆分的ID
Set<Long> splitIds = new HashSet<>();
splitIds.addAll(Ids);
if (splitIds.isEmpty()) {
throw new ServiceException("待拆分库位ID列表不能为空");
}
// 根据列标识查询该列的所有库位(第一层和第二层)
List<WmsActualWarehouse> columnLocations = baseMapper.selectList(Wrappers.<WmsActualWarehouse>lambdaQuery()
.likeRight(WmsActualWarehouse::getActualWarehouseCode, columnFlag + "-")
.eq(WmsActualWarehouse::getDelFlag, 0)
.eq(WmsActualWarehouse::getActualWarehouseType, 3) // 只查询三级库位
.orderByAsc(WmsActualWarehouse::getActualWarehouseCode));
if (CollUtil.isEmpty(columnLocations)) {
throw new ServiceException("未找到列标识为 " + columnFlag + " 的库位");
}
// 检查整个列中是否有任何库位被占用
List<String> occupiedCodes = new ArrayList<>();
for (WmsActualWarehouse location : columnLocations) {
// 锁定状态=0 → 被占用
boolean isOccupied = location.getIsEnabled() != null && location.getIsEnabled() == 0;
if (isOccupied) {
occupiedCodes.add(location.getActualWarehouseCode());
}
}
// 如果有任何库位被占用,抛出异常提示
if (!occupiedCodes.isEmpty()) {
throw new ServiceException("以下库位被占用,无法拆分:" + String.join(",", occupiedCodes));
}
// 批量查询所有子库位(一次性查询,避免循环查询)
List<Long> parentIds = columnLocations.stream()
.map(WmsActualWarehouse::getActualWarehouseId)
.collect(Collectors.toList());
List<WmsActualWarehouse> allChildren = baseMapper.selectList(Wrappers.<WmsActualWarehouse>lambdaQuery()
.in(WmsActualWarehouse::getParentId, parentIds)
.eq(WmsActualWarehouse::getActualWarehouseType, 4)); // 四级子库位
boolean hasExistingChildren = CollUtil.isNotEmpty(allChildren);
if (hasExistingChildren) {
// 如果已经有子库位,执行复活逻辑
reviveExistingSubLocations(columnLocations, splitIds, splitType, allChildren);
} else {
// 如果没有子库位,执行创建逻辑
createNewSubLocationsWithOriginalLogic(columnLocations, splitIds, splitType, columnFlag);
}
// 批量更新该列所有被拆分库位的状态
List<WmsActualWarehouse> toUpdate = new ArrayList<>();
for (WmsActualWarehouse location : columnLocations) {
WmsActualWarehouse upd = new WmsActualWarehouse();
upd.setActualWarehouseId(location.getActualWarehouseId());
upd.setSplitStatus(1);
upd.setSplitType(splitType);
toUpdate.add(upd);
}
if (!toUpdate.isEmpty()) {
baseMapper.updateBatchById(toUpdate);
}
}
/**
* 复活已存在的子库位(优化版:批量操作)
*/
private void reviveExistingSubLocations(List<WmsActualWarehouse> columnLocations, Set<Long> splitIds, Integer splitType, List<WmsActualWarehouse> allChildren) {
// 按父ID分组子库位
Map<Long, List<WmsActualWarehouse>> childrenByParent = allChildren.stream()
.sorted(Comparator.comparing(WmsActualWarehouse::getActualWarehouseId))
.collect(Collectors.groupingBy(WmsActualWarehouse::getParentId, LinkedHashMap::new, Collectors.toList()));
List<WmsActualWarehouse> toUpdate = new ArrayList<>();
for (WmsActualWarehouse location : columnLocations) {
boolean isSplitLocation = splitIds.contains(location.getActualWarehouseId());
List<WmsActualWarehouse> children = childrenByParent.get(location.getActualWarehouseId());
if (CollUtil.isEmpty(children)) {
continue;
}
// 根据是否为拆分库位决定需要复活的子库位数量
int requiredCount = isSplitLocation ? 2 : 1;
// 复活前N个子库位
for (int i = 0; i < Math.min(requiredCount, children.size()); i++) {
WmsActualWarehouse child = children.get(i);
if (child.getDelFlag() == 1) { // 只处理已删除的子库位
WmsActualWarehouse childUpdate = new WmsActualWarehouse();
childUpdate.setActualWarehouseId(child.getActualWarehouseId());
childUpdate.setDelFlag(0);
childUpdate.setSplitStatus(1);
childUpdate.setSplitType(splitType);
toUpdate.add(childUpdate);
}
}
// 如果子库位数量超过需要的数量,隐藏多余的
if (children.size() > requiredCount) {
for (int i = requiredCount; i < children.size(); i++) {
WmsActualWarehouse child = children.get(i);
if (child.getDelFlag() == 0) { // 只处理未删除的子库位
WmsActualWarehouse childUpdate = new WmsActualWarehouse();
childUpdate.setActualWarehouseId(child.getActualWarehouseId());
childUpdate.setDelFlag(1);
toUpdate.add(childUpdate);
}
}
}
}
// 批量更新
if (!toUpdate.isEmpty()) {
baseMapper.updateBatchById(toUpdate);
}
}
/**
* 使用逻辑创建新的子库位
*/
private void createNewSubLocationsWithOriginalLogic(List<WmsActualWarehouse> columnLocations, Set<Long> splitIds, Integer splitType, String columnFlag) {
// 分离需要拆分的库位和剩余库位
List<WmsActualWarehouse> splitLocations = new ArrayList<>();
List<WmsActualWarehouse> remainingLocations = new ArrayList<>();
for (WmsActualWarehouse location : columnLocations) {
if (splitIds.contains(location.getActualWarehouseId())) {
splitLocations.add(location);
} else {
remainingLocations.add(location);
}
}
// 第一步:先创建所有子库位(不设置编码)
List<WmsActualWarehouse> toInsert = new ArrayList<>();
// 1. 处理需要拆分的库位每个拆分为2个子库位
for (WmsActualWarehouse location : splitLocations) {
// 生成第一个子库位
WmsActualWarehouse subLocation1 = new WmsActualWarehouse();
subLocation1.setParentId(location.getActualWarehouseId());
subLocation1.setActualWarehouseType(4L); // 四级节点
subLocation1.setSortNo(Optional.ofNullable(location.getSortNo()).orElse(0L));
subLocation1.setIsEnabled(1);
subLocation1.setSplitStatus(1);
subLocation1.setSplitType(splitType);
toInsert.add(subLocation1);
// 生成第二个子库位
WmsActualWarehouse subLocation2 = new WmsActualWarehouse();
subLocation2.setParentId(location.getActualWarehouseId());
subLocation2.setActualWarehouseType(4L); // 四级节点
subLocation2.setSortNo(Optional.ofNullable(location.getSortNo()).orElse(0L));
subLocation2.setIsEnabled(1);
subLocation2.setSplitStatus(1);
subLocation2.setSplitType(splitType);
toInsert.add(subLocation2);
}
// 2. 处理剩余库位每个拆分为1个子库位保持原层级
for (WmsActualWarehouse remainingLocation : remainingLocations) {
// 生成剩余库位的子库位
WmsActualWarehouse remainingSubLocation = new WmsActualWarehouse();
remainingSubLocation.setParentId(remainingLocation.getActualWarehouseId());
remainingSubLocation.setActualWarehouseType(4L); // 四级节点
remainingSubLocation.setSortNo(Optional.ofNullable(remainingLocation.getSortNo()).orElse(0L));
remainingSubLocation.setIsEnabled(1);
remainingSubLocation.setSplitStatus(1);
remainingSubLocation.setSplitType(splitType);
toInsert.add(remainingSubLocation);
}
// 3. 验证总数
int expectedSplitSubLocations = splitLocations.size() * 2;
int expectedRemainingSubLocations = remainingLocations.size() * 1;
int expectedTotalSubLocations = expectedSplitSubLocations + expectedRemainingSubLocations;
int actualTotalSubLocations = toInsert.size();
if (actualTotalSubLocations != expectedTotalSubLocations) {
throw new ServiceException(String.format(
"子库位生成数量不匹配,期望%d个拆分库位%d个→%d个剩余库位%d个→%d个实际生成%d个",
expectedTotalSubLocations,
splitLocations.size(), expectedSplitSubLocations,
remainingLocations.size(), expectedRemainingSubLocations,
actualTotalSubLocations
));
}
// 4. 批量插入子库位先插入获取ID
if (!toInsert.isEmpty()) {
boolean ok = baseMapper.insertBatch(toInsert);
if (!ok) {
throw new ServiceException("新增子库位失败");
}
}
// 第二步:重新生成编码
String prefix = columnFlag;
List<WmsActualWarehouse> toUpdate = new ArrayList<>();
// 1. 先把所有子库位按父库位排序
List<WmsActualWarehouse> sortedSubLocations = new ArrayList<>(toInsert);
sortedSubLocations.sort((a, b) -> {
// 按父库位在原列中的顺序排序
Long parentA = a.getParentId();
Long parentB = b.getParentId();
int indexA = columnLocations.stream().map(WmsActualWarehouse::getActualWarehouseId).collect(Collectors.toList()).indexOf(parentA);
int indexB = columnLocations.stream().map(WmsActualWarehouse::getActualWarehouseId).collect(Collectors.toList()).indexOf(parentB);
return Integer.compare(indexA, indexB);
});
// 2. 遍历排序后的子库位每2个为一组生成编码序号递增后缀固定1、2
for (int i = 0; i < sortedSubLocations.size(); i++) {
WmsActualWarehouse sub = sortedSubLocations.get(i);
int sequenceNum = (i / 2) + 1; // 每2个子库位对应一个序号0→1,1→1,2→2,3→2...
int layer = (i % 2) + 1; // 每组内固定1、2后缀0→1,1→2,2→1,3→2...
// 生成编码:前缀-两位序号-层级如F2B1-X01-1、F2B1-X01-2、F2B1-X02-1...
String newCode = String.format("%s-X%02d-%d", prefix, sequenceNum, layer);
sub.setActualWarehouseCode(newCode);
sub.setActualWarehouseName(newCode);
toUpdate.add(sub);
}
// 3. 批量更新编码
if (!toUpdate.isEmpty()) {
baseMapper.updateBatchById(toUpdate);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void mergeLocations(WmsActualWarehouseSplitBo bo) {
if (bo == null) {
throw new ServiceException("参数不能为空");
}
// 获取列标识
String columnFlag = bo.getColumnFlag();
if (StringUtils.isBlank(columnFlag)) {
throw new ServiceException("列标识不能为空");
}
// 根据列标识查询该列的所有三级库位
List<WmsActualWarehouse> columnLocations = baseMapper.selectList(Wrappers.<WmsActualWarehouse>lambdaQuery()
.likeRight(WmsActualWarehouse::getActualWarehouseCode, columnFlag + "-")
.eq(WmsActualWarehouse::getDelFlag, 0)
.eq(WmsActualWarehouse::getActualWarehouseType, 3) // 只查询三级库位
.orderByAsc(WmsActualWarehouse::getActualWarehouseCode));
if (CollUtil.isEmpty(columnLocations)) {
throw new ServiceException("未找到列标识为 " + columnFlag + " 的库位");
}
// 过滤出需要合并的库位splitStatus=1
List<WmsActualWarehouse> locationsToMerge = columnLocations.stream()
.filter(loc -> Optional.ofNullable(loc.getSplitStatus()).orElse(0) == 1)
.collect(Collectors.toList());
if (CollUtil.isEmpty(locationsToMerge)) {
return; // 没有需要合并的库位
}
// 批量查询所有子库位
List<Long> parentIds = columnLocations.stream()
.map(WmsActualWarehouse::getActualWarehouseId)
.collect(Collectors.toList());
List<WmsActualWarehouse> allChildren = baseMapper.selectList(Wrappers.<WmsActualWarehouse>lambdaQuery()
.in(WmsActualWarehouse::getParentId, parentIds)
.eq(WmsActualWarehouse::getActualWarehouseType, 4)); // 四级子库位
// 检查整列中是否有任何子库位被占用
if (CollUtil.isNotEmpty(allChildren)) {
boolean anyOccupied = allChildren.stream()
.filter(c -> c.getDelFlag() == 0)
.anyMatch(c -> !Objects.equals(c.getIsEnabled(), 1));
if (anyOccupied) {
throw new ServiceException("该列存在被占用的子库位,不能合并");
}
}
// 批量更新子库位和父库位
List<WmsActualWarehouse> childrenToUpdate = new ArrayList<>();
List<WmsActualWarehouse> parentsToUpdate = new ArrayList<>();
for (WmsActualWarehouse location : locationsToMerge) {
List<WmsActualWarehouse> children = allChildren.stream()
.filter(c -> Objects.equals(c.getParentId(), location.getActualWarehouseId()))
.collect(Collectors.toList());
// 隐藏所有子库位
if (CollUtil.isNotEmpty(children)) {
for (WmsActualWarehouse child : children) {
if (child.getDelFlag() == 0) {
WmsActualWarehouse childUpdate = new WmsActualWarehouse();
childUpdate.setActualWarehouseId(child.getActualWarehouseId());
childUpdate.setDelFlag(1);
childrenToUpdate.add(childUpdate);
}
}
}
// 更新父库位状态为未拆分
WmsActualWarehouse parentUpdate = new WmsActualWarehouse();
parentUpdate.setActualWarehouseId(location.getActualWarehouseId());
parentUpdate.setSplitStatus(0);
parentUpdate.setSplitType(0);
parentsToUpdate.add(parentUpdate);
}
// 批量更新
if (!childrenToUpdate.isEmpty()) {
baseMapper.updateBatchById(childrenToUpdate);
}
if (!parentsToUpdate.isEmpty()) {
baseMapper.updateBatchById(parentsToUpdate);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public int fixSortNoByParentId(Long parentId) {
if (parentId == null) {
throw new ServiceException("父库位ID不能为空");
}
WmsActualWarehouse parent = baseMapper.selectById(parentId);
if (parent == null) {
throw new ServiceException("父库位不存在");
}
Long l1SortNo = parent.getSortNo() != null ? parent.getSortNo() : 0L;
int[] counter = {1}; // Global counter for sortNo
if (parent.getActualWarehouseType() != null && parent.getActualWarehouseType() == 1L) {
List<WmsActualWarehouse> l2List = baseMapper.selectList(
new LambdaQueryWrapper<WmsActualWarehouse>()
.eq(WmsActualWarehouse::getParentId, parentId)
.eq(WmsActualWarehouse::getDelFlag, 0)
);
int totalCount = 0;
for (WmsActualWarehouse l2 : l2List) {
Long l2SortNo = l2.getSortNo() != null ? l2.getSortNo() : 0L;
totalCount += fixLevel(l2.getActualWarehouseId(), l1SortNo, l2SortNo, counter);
}
return totalCount;
} else {
Long l2SortNo = 0L;
if (parent.getParentId() != null) {
WmsActualWarehouse l2 = baseMapper.selectById(parent.getParentId());
l2SortNo = l2 != null && l2.getSortNo() != null ? l2.getSortNo() : 0L;
}
return fixLevel(parentId, l1SortNo, l2SortNo, counter);
}
}
private int fixLevel(Long parentId, Long l1SortNo, Long l2SortNo, int[] counter) {
List<WmsActualWarehouse> list = baseMapper.selectList(
new LambdaQueryWrapper<WmsActualWarehouse>()
.eq(WmsActualWarehouse::getParentId, parentId)
.eq(WmsActualWarehouse::getDelFlag, 0)
);
if (list.isEmpty()) return 0;
// 需求调整:此处不再对四级(小库位)进行 sort_no 修正,只修正三级(大库位)即可
boolean isFourthLevel = list.get(0).getActualWarehouseType() != null
&& list.get(0).getActualWarehouseType() == 4L;
if (isFourthLevel) {
return 0;
}
Map<WmsActualWarehouse, int[]> sortKeyMap = new HashMap<>();
Pattern pattern3 = Pattern.compile("(.*?)(\\d+)-(\\d+)-(\\d+)");
for (WmsActualWarehouse w : list) {
String code = w.getActualWarehouseCode();
if (StringUtils.isBlank(code)) continue;
Matcher m3 = pattern3.matcher(code);
if (m3.matches()) {
sortKeyMap.put(w, new int[]{
Integer.parseInt(m3.group(2)),
Integer.parseInt(m3.group(3)),
Integer.parseInt(m3.group(4))
});
}
}
List<WmsActualWarehouse> sorted = sortKeyMap.entrySet().stream()
.sorted((e1, e2) -> {
int[] k1 = e1.getValue();
int[] k2 = e2.getValue();
int c = Integer.compare(k1[0], k2[0]);
if (c != 0) return c;
c = Integer.compare(k1[1], k2[1]);
if (c != 0) return c;
return Integer.compare(k1[2], k2[2]);
})
.map(Map.Entry::getKey)
.collect(Collectors.toList());
List<WmsActualWarehouse> updates = new ArrayList<>();
int totalCount = 0;
for (WmsActualWarehouse w : sorted) {
long currentSortNo = l1SortNo * 100000L + l2SortNo * 10000L + counter[0];
// 仅修正三级排序号;四级交错排序已由钢卷查询排序逻辑处理
totalCount += fixLevel(w.getActualWarehouseId(), l1SortNo, l2SortNo, counter);
WmsActualWarehouse update = new WmsActualWarehouse();
update.setActualWarehouseId(w.getActualWarehouseId());
update.setSortNo(currentSortNo);
updates.add(update);
counter[0]++;
totalCount++;
if (updates.size() >= 500) {
baseMapper.updateBatchById(updates);
updates.clear();
}
}
if (!updates.isEmpty()) {
baseMapper.updateBatchById(updates);
}
return totalCount;
}
}