feat(wms): 重构库位拆分逻辑并支持列标识拆分

- 修改查询排序字段为实际库位编码
- 新增列标识参数用于指定拆分列
- 支持按列标识批量拆分库位
- 优化拆分逻辑,支持不同拆分类型
- 增加拆分数量验证机制
- 完善异常处理和参数校验
- 调整实体类字段约束和结构
This commit is contained in:
2025-12-20 09:55:56 +08:00
parent a178ee4f5e
commit 3ba87bd507
2 changed files with 155 additions and 95 deletions

View File

@@ -15,7 +15,6 @@ public class WmsActualWarehouseSplitBo {
/**
* 需要操作的三级库位ID集合大库位ID
*/
@NotEmpty
private List<Long> locationIds;
/**
@@ -25,7 +24,18 @@ public class WmsActualWarehouseSplitBo {
private Integer action;
/**
* 拆分类型:目前仅支持 0=列拆分
* 拆分类型:0=1列拆2小库位1=(1列拆3小库位)
*/
private Integer splitType = 0;
/**
* 列标识如F3A1表示第一列F3A2表示第二列
*/
@NotEmpty
private String columnFlag;
/**
* 待拆分ID列表
*/
private List<Long> splitIds;
}

View File

@@ -42,7 +42,7 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
* 查询实际库区/库位自关联
*/
@Override
public WmsActualWarehouseVo queryById(Long actualWarehouseId){
public WmsActualWarehouseVo queryById(Long actualWarehouseId) {
return baseMapper.selectVoById(actualWarehouseId);
}
@@ -136,7 +136,7 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
Wrappers.<WmsActualWarehouse>lambdaQuery()
.in(WmsActualWarehouse::getParentId, splitParentIds)
.eq(WmsActualWarehouse::getDelFlag, 0)
.orderByAsc(WmsActualWarehouse::getSortNo, WmsActualWarehouse::getActualWarehouseId)
.orderByAsc(WmsActualWarehouse::getActualWarehouseCode, WmsActualWarehouse::getActualWarehouseId)
);
Map<Long, List<WmsActualWarehouseVo>> childrenMap = children.stream()
.collect(Collectors.groupingBy(WmsActualWarehouseVo::getParentId, LinkedHashMap::new, Collectors.toList()));
@@ -237,6 +237,7 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
});
return roots;
}
@Override
public List<WmsActualWarehouseTreeVo> queryTreeExcludeLevelThree(WmsActualWarehouseBo bo) {
if (bo == null) {
@@ -387,7 +388,7 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
/**
* 保存前的数据校验
*/
private void validEntityBeforeSave(WmsActualWarehouse entity){
private void validEntityBeforeSave(WmsActualWarehouse entity) {
//TODO 做一些数据校验,如唯一约束
}
@@ -413,7 +414,7 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
*/
@Override
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
if(isValid){
if (isValid) {
//TODO 做一些业务上的校验,判断是否需要校验
}
return baseMapper.deleteBatchIds(ids) > 0;
@@ -422,106 +423,155 @@ public class WmsActualWarehouseServiceImpl implements IWmsActualWarehouseService
@Override
@Transactional(rollbackFor = Exception.class)
public void splitLocations(WmsActualWarehouseSplitBo bo) {
if (bo == null || CollUtil.isEmpty(bo.getLocationIds())) {
if (bo == null) {
throw new ServiceException("参数不能为空");
}
int splitType = Optional.ofNullable(bo.getSplitType()).orElse(0);
// 仅允许列拆分1拆2的类型防止非法拆分类型
if (splitType != 0) {
throw new ServiceException("暂仅支持列拆分1拆2");
}
// 查询父库位并校验有效性
List<Long> locationIds = bo.getLocationIds();
List<WmsActualWarehouse> parents = baseMapper.selectList(Wrappers.<WmsActualWarehouse>lambdaQuery()
.in(WmsActualWarehouse::getActualWarehouseId, locationIds)
.eq(WmsActualWarehouse::getDelFlag, 0));
// 校验无效ID并明确提示
Set<Long> existIds = parents.stream().map(WmsActualWarehouse::getActualWarehouseId).collect(Collectors.toSet());
List<Long> invalidIds = locationIds.stream().filter(id -> !existIds.contains(id)).collect(Collectors.toList());
if (!invalidIds.isEmpty()) {
throw new ServiceException("存在无效库位ID" + invalidIds);
}
// 检查占用与状态:仅依据 isEnabled=1 为未占用,可拆分
for (WmsActualWarehouse p : parents) {
Integer st = Optional.ofNullable(p.getSplitStatus()).orElse(0);
if (st == 1) {
throw new ServiceException("库位已拆分:" + p.getActualWarehouseCode());
}
if (!Objects.equals(p.getIsEnabled(), 1)) {
throw new ServiceException("库位被占用或禁用,不能拆分:" + p.getActualWarehouseCode());
}
}
List<WmsActualWarehouse> toInsert = new ArrayList<>();
for (WmsActualWarehouse p : parents) {
String base = p.getActualWarehouseCode();
// 先查找是否存在历史子库位(逻辑删除状态)
List<WmsActualWarehouse> historyChildren = baseMapper.selectList(
Wrappers.<WmsActualWarehouse>lambdaQuery()
.eq(WmsActualWarehouse::getParentId, p.getActualWarehouseId())
.in(WmsActualWarehouse::getActualWarehouseCode, Arrays.asList(base + "-1", base + "-2"))
.eq(WmsActualWarehouse::getDelFlag, 1)
);
if (CollUtil.isNotEmpty(historyChildren)) {
// 复活历史子库位避免新建保持原ID和二维码
for (WmsActualWarehouse his : historyChildren) {
WmsActualWarehouse revive = new WmsActualWarehouse();
revive.setActualWarehouseId(his.getActualWarehouseId());
revive.setDelFlag(0);
revive.setSplitStatus(1);
revive.setSplitType(splitType);
revive.setIsEnabled(1);
baseMapper.updateById(revive);
}
// 如果只找到了一个历史子库位,另一个需要新建
if (historyChildren.size() < 2) {
Set<String> found = historyChildren.stream().map(WmsActualWarehouse::getActualWarehouseCode).collect(Collectors.toSet());
String miss = !found.contains(base + "-1") ? base + "-1" : base + "-2";
WmsActualWarehouse c = new WmsActualWarehouse();
c.setParentId(p.getActualWarehouseId());
c.setActualWarehouseType(4L); // 设置为四级节点
c.setActualWarehouseCode(miss);
c.setActualWarehouseName(miss);
c.setSortNo(Optional.ofNullable(p.getSortNo()).orElse(0L));
c.setIsEnabled(1);
c.setSplitStatus(1);
c.setSplitType(splitType);
toInsert.add(c);
}
} else {
// 无历史记录,则正常新建两个
WmsActualWarehouse c1 = new WmsActualWarehouse();
c1.setParentId(p.getActualWarehouseId());
c1.setActualWarehouseType(4L); // 设置为四级节点
c1.setActualWarehouseCode(base + "-1");
c1.setActualWarehouseName(base + "-1");
c1.setSortNo(Optional.ofNullable(p.getSortNo()).orElse(0L));
c1.setIsEnabled(1);
c1.setSplitStatus(1);
c1.setSplitType(splitType);
toInsert.add(c1);
WmsActualWarehouse c2 = new WmsActualWarehouse();
c2.setParentId(p.getActualWarehouseId());
c2.setActualWarehouseType(4L); // 设置为四级节点
c2.setActualWarehouseCode(base + "-2");
c2.setActualWarehouseName(base + "-2");
c2.setSortNo(Optional.ofNullable(p.getSortNo()).orElse(0L));
c2.setIsEnabled(1);
c2.setSplitStatus(1);
c2.setSplitType(splitType);
toInsert.add(c2);
// 参数验证
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()
.like(WmsActualWarehouse::getActualWarehouseCode, columnFlag)
.eq(WmsActualWarehouse::getDelFlag, 0)
.eq(WmsActualWarehouse::getActualWarehouseType, 3) // 只查询三级库位
.orderByAsc(WmsActualWarehouse::getActualWarehouseCode));
if (CollUtil.isEmpty(columnLocations)) {
throw new ServiceException("未找到列标识为 " + 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) {
String baseCode = remainingLocation.getActualWarehouseCode();
// 生成剩余库位的子库位
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("新增库位失败");
throw new ServiceException("新增库位失败");
}
}
// 更新父节点拆分状态
for (WmsActualWarehouse p : parents) {
// 第二步:重新生成编码
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()) {
for (WmsActualWarehouse item : toUpdate) {
baseMapper.updateById(item);
}
}
// 6. 更新该列所有被拆分库位的状态
for (WmsActualWarehouse location : columnLocations) {
WmsActualWarehouse upd = new WmsActualWarehouse();
upd.setActualWarehouseId(p.getActualWarehouseId());
upd.setActualWarehouseId(location.getActualWarehouseId());
upd.setSplitStatus(1);
upd.setSplitType(splitType);
baseMapper.updateById(upd);