feat(wms): 实现钢卷合卷分卷回滚功能

- 新增解析二维码内容获取回滚信息的方法,支持识别合卷、分卷和普通更新操作
- 实现合卷回滚逻辑,恢复原始钢卷并删除合卷钢卷,释放占用的实际库区
- 实现分卷回滚逻辑,删除子钢卷并恢复母卷为当前数据
- 重构普通更新回滚逻辑,优化错误处理和参数传递
- 新增合卷分卷回滚后的二维码记录更新方法
- 完善回滚操作的错误检查和状态验证机制
- 优化回滚过程中的日志记录和异常处理
This commit is contained in:
2026-03-04 10:33:26 +08:00
parent 9bc0fb65c0
commit 8a8548bbb6

View File

@@ -1902,7 +1902,7 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
}
}
}
return baseMapper.deleteBatchIds(ids) > 0;
}
@@ -2986,101 +2986,432 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
throw new RuntimeException("二维码记录不存在或内容为空,无法回滚");
}
// 5. 解析二维码内容,找到上一步的历史钢卷ID
Long historyCoilId = parseHistoryCoilIdFromQrcode(qrcodeRecord.getContent(), currentCoilId);
if (historyCoilId == null) {
// 检查无法回滚的具体原因
String rollbackBlockReason = getRollbackBlockReason(qrcodeRecord.getContent(), currentCoilId);
switch (rollbackBlockReason) {
case "INITIAL_CREATE":
throw new RuntimeException("无法继续回滚:当前钢卷为最初的新增操作创建,如需删除请直接使用删除功能");
case "MERGE_PRODUCT":
throw new RuntimeException("无法回滚:当前钢卷是合卷操作的产物,回滚会导致其他相关钢卷数据异常");
case "SPLIT_PRODUCT":
throw new RuntimeException("无法回滚:当前钢卷是分卷操作的产物,回滚会导致其他相关钢卷数据异常");
default:
throw new RuntimeException("无法从二维码记录中找到历史钢卷ID无法回滚");
}
// 5. 解析二维码内容,判断是合卷、分卷还是普通更新
Map<String, Object> rollbackInfo = parseRollbackInfoFromQrcode(qrcodeRecord.getContent(), currentCoilId);
String operationType = (String) rollbackInfo.get("operationType");
// 6. 根据操作类型执行不同的回滚逻辑
if ("MERGE".equals(operationType)) {
// 合卷回滚
return rollbackMergeOperation(currentCoil, qrcodeRecord, rollbackInfo, result);
} else if ("SPLIT".equals(operationType)) {
// 分卷回滚
return rollbackSplitOperation(currentCoil, qrcodeRecord, rollbackInfo, result);
} else {
// 普通更新回滚(原有逻辑)
return rollbackNormalOperation(currentCoil, qrcodeRecord, result);
}
// 6. 查询历史钢卷信息
WmsMaterialCoil historyCoil = baseMapper.selectById(historyCoilId);
if (historyCoil == null) {
throw new RuntimeException("历史钢卷不存在,无法回滚");
}
// 7. 检查历史钢卷是否为历史数据
if (historyCoil.getDataType() != 0) {
throw new RuntimeException("目标钢卷不是历史数据,无法恢复");
}
// 8. 检查历史钢卷的当前钢卷号是否重复
Map<String, Object> duplicateCheck = checkCoilNoDuplicate(currentCoilId, historyCoil.getEnterCoilNo(), historyCoil.getCurrentCoilNo());
// 仅校验当前钢卷号是否重复,忽略入场钢卷号重复
boolean currentCoilNoDuplicate = (boolean) duplicateCheck.get("currentCoilNoDuplicate");
if (currentCoilNoDuplicate) {
String errorMsg = "无法恢复历史钢卷,存在重复的钢卷号:历史钢卷的当前钢卷号[" + historyCoil.getCurrentCoilNo() + "]重复。";
errorMsg += "重复的钢卷无法进行回滚操作。";
throw new RuntimeException(errorMsg);
}
// 9. 执行回滚操作
// 9.1 释放当前钢卷占用的实际库区
if (currentCoil.getActualWarehouseId() != null) {
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
releaseBo.setActualWarehouseId(currentCoil.getActualWarehouseId());
releaseBo.setIsEnabled(1); // 设置为启用状态
actualWarehouseService.updateByBo(releaseBo);
}
// 9.2 删除当前钢卷
baseMapper.deleteById(currentCoilId);
// 删除操作记录删除最晚的一条且actionType不在401-405范围内并且coilId等于historyCoilId
LambdaQueryWrapper<WmsCoilPendingAction> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(WmsCoilPendingAction::getCoilId, historyCoilId)
.notIn(WmsCoilPendingAction::getActionType, Arrays.asList(401, 402, 403, 404, 405))
.eq(WmsCoilPendingAction::getDelFlag, 0)
.eq(WmsCoilPendingAction::getActionStatus, 2)
.orderByDesc(WmsCoilPendingAction::getCreateTime)
.last("LIMIT 1");
WmsCoilPendingAction latestAction = coilPendingActionMapper.selectOne(queryWrapper);
if (latestAction != null) {
coilPendingActionMapper.deleteById(latestAction.getActionId());
}
// 9.3 恢复历史钢卷为当前数据
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, historyCoilId)
.set(WmsMaterialCoil::getDataType, 1) // 恢复为当前数据
.set(WmsMaterialCoil::getActualWarehouseId, (Long) null); // 清空实际库区绑定
baseMapper.update(null, updateWrapper);
// 9.4 更新二维码记录,添加回滚步骤并更新相关字段
updateQrcodeForRollback(qrcodeRecord, currentCoil, historyCoil);
// 10. 重新查询恢复后的历史钢卷信息
WmsMaterialCoilVo restoredCoil = queryById(historyCoilId);
// 11. 返回操作结果
result.put("success", true);
result.put("restoredCoil", restoredCoil);
result.put("deletedCoilId", currentCoilId);
result.put("message", "回滚操作成功,已恢复历史钢卷,删除了当前钢卷");
log.info("钢卷回滚操作成功历史钢卷ID={}, 删除的钢卷ID={}", historyCoilId, currentCoilId);
} catch (Exception e) {
log.error("钢卷回滚操作失败currentCoilId={}", currentCoilId, e);
result.put("success", false);
result.put("message", "回滚操作失败:" + e.getMessage());
throw e; // 重新抛出异常,让事务回滚
throw e;
}
}
/**
* 解析二维码内容,获取回滚所需的信息
* @return 包含operationType和相应参数的Map
*/
private Map<String, Object> parseRollbackInfoFromQrcode(String content, Long currentCoilId) {
Map<String, Object> result = new HashMap<>();
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> contentMap = objectMapper.readValue(content, Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null || steps.isEmpty()) {
result.put("operationType", "NORMAL");
return result;
}
// 从后往前查找最新的操作
for (int i = steps.size() - 1; i >= 0; i--) {
Map<String, Object> step = steps.get(i);
String action = (String) step.get("action");
String operation = (String) step.get("operation");
// 跳过新增和回滚操作
if ("新增".equals(action) || "回滚".equals(action)) {
continue;
}
// 检查是否是合卷操作
if ("合卷".equals(operation)) {
Object parentCoilIdsObj = step.get("parent_coil_ids");
if (parentCoilIdsObj != null) {
String parentCoilIdsStr = parentCoilIdsObj.toString();
if (StringUtils.isNotBlank(parentCoilIdsStr)) {
List<Long> parentCoilIds = Arrays.stream(parentCoilIdsStr.split(","))
.map(String::trim)
.filter(s -> StringUtils.isNotBlank(s))
.map(Long::parseLong)
.collect(Collectors.toList());
result.put("operationType", "MERGE");
result.put("parentCoilIds", parentCoilIds);
result.put("targetStep", step);
return result;
}
}
}
// 检查是否是分卷操作
if ("分卷".equals(operation)) {
Object childCoilIdsObj = step.get("child_coil_ids");
if (childCoilIdsObj != null) {
String childCoilIdsStr = childCoilIdsObj.toString();
if (StringUtils.isNotBlank(childCoilIdsStr)) {
List<Long> childCoilIds = Arrays.stream(childCoilIdsStr.split(","))
.map(String::trim)
.filter(s -> StringUtils.isNotBlank(s))
.map(Long::parseLong)
.collect(Collectors.toList());
result.put("operationType", "SPLIT");
result.put("childCoilIds", childCoilIds);
result.put("targetStep", step);
return result;
}
}
}
// 如果找到普通更新操作
Object newCoilIdObj = step.get("new_coil_id");
if (newCoilIdObj != null && newCoilIdObj.toString().equals(currentCoilId.toString())) {
Object oldCoilIdObj = step.get("old_coil_id");
if (oldCoilIdObj != null) {
result.put("operationType", "NORMAL");
result.put("historyCoilId", Long.parseLong(oldCoilIdObj.toString()));
return result;
}
}
}
// 如果没找到任何操作,返回普通类型
result.put("operationType", "NORMAL");
return result;
} catch (Exception e) {
log.error("解析二维码内容失败", e);
result.put("operationType", "NORMAL");
return result;
}
}
/**
* 回滚合卷操作
* 获取所有被合并的钢卷ID将这些钢卷还原dataType改为1并删除当前合卷的钢卷
*/
private Map<String, Object> rollbackMergeOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord,
Map<String, Object> rollbackInfo, Map<String, Object> result) {
List<Long> parentCoilIds = (List<Long>) rollbackInfo.get("parentCoilIds");
if (parentCoilIds == null || parentCoilIds.isEmpty()) {
throw new RuntimeException("无法获取合卷的原始钢卷ID无法回滚");
}
// 查询所有原始钢卷
List<WmsMaterialCoil> originalCoils = baseMapper.selectBatchIds(parentCoilIds);
// 检查所有原始钢卷是否存在
if (originalCoils.size() != parentCoilIds.size()) {
throw new RuntimeException("部分原始钢卷不存在,无法回滚");
}
// 释放当前合卷钢卷占用的实际库区
if (currentCoil.getActualWarehouseId() != null) {
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
releaseBo.setActualWarehouseId(currentCoil.getActualWarehouseId());
releaseBo.setIsEnabled(1);
actualWarehouseService.updateByBo(releaseBo);
}
// 删除当前合卷钢卷
baseMapper.deleteById(currentCoil.getCoilId());
// 将所有原始钢卷恢复为当前数据,实际库区置空
for (WmsMaterialCoil originalCoil : originalCoils) {
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, originalCoil.getCoilId())
.set(WmsMaterialCoil::getDataType, 1)
.set(WmsMaterialCoil::getActualWarehouseId, (Long) null);
baseMapper.update(null, updateWrapper);
}
// 更新二维码记录
updateQrcodeForMergeRollback(qrcodeRecord, currentCoil, parentCoilIds);
result.put("success", true);
result.put("message", "合卷回滚成功,已恢复 " + parentCoilIds.size() + " 个原始钢卷,删除了合卷钢卷");
result.put("restoredCoilCount", parentCoilIds.size());
result.put("deletedCoilId", currentCoil.getCoilId());
log.info("合卷回滚操作成功删除的合卷钢卷ID={}, 恢复的原始钢卷ID={}", currentCoil.getCoilId(), parentCoilIds);
return result;
}
/**
* 回滚分卷操作
* 删除所有母卷生成的子钢卷,恢复母卷为当前数据
*/
private Map<String, Object> rollbackSplitOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord,
Map<String, Object> rollbackInfo, Map<String, Object> result) {
List<Long> childCoilIds = (List<Long>) rollbackInfo.get("childCoilIds");
if (childCoilIds == null || childCoilIds.isEmpty()) {
throw new RuntimeException("无法获取分卷的子钢卷ID无法回滚");
}
// 查询所有子钢卷
List<WmsMaterialCoil> childCoils = baseMapper.selectBatchIds(childCoilIds);
// 检查所有子钢卷是否存在
if (childCoils.size() != childCoilIds.size()) {
throw new RuntimeException("部分子钢卷不存在,无法回滚");
}
// 找到母卷
Map<String, Object> targetStep = (Map<String, Object>) rollbackInfo.get("targetStep");
Object oldCoilIdObj = targetStep.get("old_coil_id");
Long motherCoilId = null;
if (oldCoilIdObj != null) {
motherCoilId = Long.parseLong(oldCoilIdObj.toString());
}
// 检查母卷的独占状态
if (motherCoilId != null) {
WmsMaterialCoil motherCoil = baseMapper.selectById(motherCoilId);
if (motherCoil != null && motherCoil.getExclusiveStatus() != null && motherCoil.getExclusiveStatus() == 1) {
throw new RuntimeException("母卷[" + motherCoil.getCurrentCoilNo() + "]正在进行单步分卷操作,无法回滚");
}
}
// 检查是否有子钢卷的dataType不等于1不是现存的钢卷
List<String> invalidCoilNos = new ArrayList<>();
for (WmsMaterialCoil childCoil : childCoils) {
if (childCoil.getDataType() != 1) {
invalidCoilNos.add(childCoil.getCurrentCoilNo() + "(历史钢卷)");
}
// 检查子钢卷的独占状态
if (childCoil.getExclusiveStatus() != null && childCoil.getExclusiveStatus() == 1) {
invalidCoilNos.add(childCoil.getCurrentCoilNo() + "(正在单步分卷)");
}
}
if (!invalidCoilNos.isEmpty()) {
throw new RuntimeException("以下钢卷不是现存钢卷,无法回滚:" + String.join(", ", invalidCoilNos));
}
// 释放所有子钢卷占用的实际库区并删除所有子钢卷
for (WmsMaterialCoil childCoil : childCoils) {
if (childCoil.getActualWarehouseId() != null) {
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
releaseBo.setActualWarehouseId(childCoil.getActualWarehouseId());
releaseBo.setIsEnabled(1);
actualWarehouseService.updateByBo(releaseBo);
}
baseMapper.deleteById(childCoil.getCoilId());
}
if (motherCoilId != null) {
// 恢复母卷为当前数据,实际库区置空
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, motherCoilId)
.set(WmsMaterialCoil::getDataType, 1)
.set(WmsMaterialCoil::getActualWarehouseId, (Long) null);
baseMapper.update(null, updateWrapper);
// 更新二维码记录
updateQrcodeForSplitRollback(qrcodeRecord, currentCoil, motherCoilId);
}
result.put("success", true);
result.put("message", "分卷回滚成功,已恢复母卷,删除了 " + childCoilIds.size() + " 个子钢卷");
result.put("deletedCoilIds", childCoilIds);
result.put("restoredCoilId", motherCoilId);
log.info("分卷回滚操作成功删除的子卷ID={}, 恢复的母卷ID={}", childCoilIds, motherCoilId);
return result;
}
/**
* 回滚普通更新操作(原有逻辑)
*/
private Map<String, Object> rollbackNormalOperation(WmsMaterialCoil currentCoil, WmsGenerateRecordVo qrcodeRecord, Map<String, Object> result) {
// 解析二维码内容找到上一步的历史钢卷ID
Long historyCoilId = parseHistoryCoilIdFromQrcode(qrcodeRecord.getContent(), currentCoil.getCoilId());
if (historyCoilId == null) {
// 检查无法回滚的具体原因
String rollbackBlockReason = getRollbackBlockReason(qrcodeRecord.getContent(), currentCoil.getCoilId());
switch (rollbackBlockReason) {
case "INITIAL_CREATE":
throw new RuntimeException("无法继续回滚:当前钢卷为最初的新增操作创建,如需删除请直接使用删除功能");
default:
throw new RuntimeException("无法从二维码记录中找到历史钢卷ID无法回滚");
}
}
// 查询历史钢卷信息
WmsMaterialCoil historyCoil = baseMapper.selectById(historyCoilId);
if (historyCoil == null) {
throw new RuntimeException("历史钢卷不存在,无法回滚");
}
// 检查历史钢卷是否为历史数据
if (historyCoil.getDataType() != 0) {
throw new RuntimeException("目标钢卷不是历史数据,无法恢复");
}
// 检查历史钢卷的当前钢卷号是否重复
Map<String, Object> duplicateCheck = checkCoilNoDuplicate(currentCoil.getCoilId(), historyCoil.getEnterCoilNo(), historyCoil.getCurrentCoilNo());
boolean currentCoilNoDuplicate = (boolean) duplicateCheck.get("currentCoilNoDuplicate");
if (currentCoilNoDuplicate) {
String errorMsg = "无法恢复历史钢卷,存在重复的钢卷号:历史钢卷的当前钢卷号[" + historyCoil.getCurrentCoilNo() + "]重复。";
errorMsg += "重复的钢卷无法进行回滚操作。";
throw new RuntimeException(errorMsg);
}
// 执行回滚操作
// 1. 释放当前钢卷占用的实际库区
if (currentCoil.getActualWarehouseId() != null) {
WmsActualWarehouseBo releaseBo = new WmsActualWarehouseBo();
releaseBo.setActualWarehouseId(currentCoil.getActualWarehouseId());
releaseBo.setIsEnabled(1);
actualWarehouseService.updateByBo(releaseBo);
}
// 2. 删除当前钢卷
baseMapper.deleteById(currentCoil.getCoilId());
// 3. 删除操作记录
LambdaQueryWrapper<WmsCoilPendingAction> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(WmsCoilPendingAction::getCoilId, historyCoilId)
.notIn(WmsCoilPendingAction::getActionType, Arrays.asList(401, 402, 403, 404, 405))
.eq(WmsCoilPendingAction::getDelFlag, 0)
.eq(WmsCoilPendingAction::getActionStatus, 2)
.orderByDesc(WmsCoilPendingAction::getCreateTime)
.last("LIMIT 1");
WmsCoilPendingAction latestAction = coilPendingActionMapper.selectOne(queryWrapper);
if (latestAction != null) {
coilPendingActionMapper.deleteById(latestAction.getActionId());
}
// 4. 恢复历史钢卷为当前数据
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, historyCoilId)
.set(WmsMaterialCoil::getDataType, 1)
.set(WmsMaterialCoil::getActualWarehouseId, (Long) null);
baseMapper.update(null, updateWrapper);
// 5. 更新二维码记录
updateQrcodeForRollback(qrcodeRecord, currentCoil, historyCoil);
// 6. 重新查询恢复后的历史钢卷信息
WmsMaterialCoilVo restoredCoil = queryById(historyCoilId);
// 7. 返回操作结果
result.put("success", true);
result.put("restoredCoil", restoredCoil);
result.put("deletedCoilId", currentCoil.getCoilId());
result.put("message", "回滚操作成功,已恢复历史钢卷,删除了当前钢卷");
log.info("钢卷回滚操作成功历史钢卷ID={}, 删除的钢卷ID={}", historyCoilId, currentCoil.getCoilId());
return result;
}
/**
* 更新合卷回滚后的二维码记录
*/
private void updateQrcodeForMergeRollback(WmsGenerateRecordVo qrcodeRecord, WmsMaterialCoil currentCoil, List<Long> parentCoilIds) {
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null) {
steps = new ArrayList<>();
}
// 添加回滚步骤
Map<String, Object> rollbackStep = new HashMap<>();
rollbackStep.put("step", steps.size() + 1);
rollbackStep.put("action", "回滚");
rollbackStep.put("operation", "合卷回滚");
rollbackStep.put("deleted_coil_id", String.valueOf(currentCoil.getCoilId()));
rollbackStep.put("deleted_current_coil_no", currentCoil.getCurrentCoilNo());
rollbackStep.put("restored_parent_coil_ids", parentCoilIds.stream().map(String::valueOf).collect(Collectors.joining(",")));
rollbackStep.put("operator", LoginHelper.getUsername());
rollbackStep.put("rollback_time", new java.util.Date());
steps.add(rollbackStep);
contentMap.put("steps", steps);
// 更新current_coil_id为第一个恢复的原始钢卷ID
if (!parentCoilIds.isEmpty()) {
contentMap.put("current_coil_id", String.valueOf(parentCoilIds.get(0)));
}
String newContentJson = objectMapper.writeValueAsString(contentMap);
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
updateBo.setRecordId(qrcodeRecord.getRecordId());
updateBo.setContent(newContentJson);
generateRecordService.updateByBo(updateBo);
} catch (Exception e) {
log.error("更新合卷回滚二维码记录失败", e);
throw new RuntimeException("更新二维码记录失败: " + e.getMessage());
}
}
/**
* 更新分卷回滚后的二维码记录
*/
private void updateQrcodeForSplitRollback(WmsGenerateRecordVo qrcodeRecord, WmsMaterialCoil currentCoil, Long motherCoilId) {
try {
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class);
@SuppressWarnings("unchecked")
List<Map<String, Object>> steps = (List<Map<String, Object>>) contentMap.get("steps");
if (steps == null) {
steps = new ArrayList<>();
}
// 添加回滚步骤
Map<String, Object> rollbackStep = new HashMap<>();
rollbackStep.put("step", steps.size() + 1);
rollbackStep.put("action", "回滚");
rollbackStep.put("operation", "分卷回滚");
rollbackStep.put("deleted_coil_id", String.valueOf(currentCoil.getCoilId()));
rollbackStep.put("deleted_current_coil_no", currentCoil.getCurrentCoilNo());
rollbackStep.put("restored_coil_id", String.valueOf(motherCoilId));
rollbackStep.put("operator", LoginHelper.getUsername());
rollbackStep.put("rollback_time", new java.util.Date());
steps.add(rollbackStep);
contentMap.put("steps", steps);
// 更新current_coil_id为恢复的母卷ID
contentMap.put("current_coil_id", String.valueOf(motherCoilId));
String newContentJson = objectMapper.writeValueAsString(contentMap);
WmsGenerateRecordBo updateBo = new WmsGenerateRecordBo();
updateBo.setRecordId(qrcodeRecord.getRecordId());
updateBo.setContent(newContentJson);
generateRecordService.updateByBo(updateBo);
} catch (Exception e) {
log.error("更新分卷回滚二维码记录失败", e);
throw new RuntimeException("更新二维码记录失败: " + e.getMessage());
}
}
/**
* 从二维码内容中解析历史钢卷ID
* 根据当前钢卷ID找到对应的上一步操作中的old_coil_id