From 1f284d36078aed999189eef92ed2649f679bd1c8 Mon Sep 17 00:00:00 2001 From: Joshi <3040996759@qq.com> Date: Tue, 20 Jan 2026 09:57:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(wms):=20=E6=B7=BB=E5=8A=A0=E9=92=A2?= =?UTF-8?q?=E5=8D=B7=E5=9B=9E=E6=BB=9A=E5=8A=9F=E8=83=BD=E6=9B=BF=E4=BB=A3?= =?UTF-8?q?=E5=8E=9F=E6=9C=89=E7=9A=84=E5=A4=8D=E6=B4=BB=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将原有的reviveCoil方法重命名为rollbackCoil,实现更精确的回滚操作 - 修改接口路径从GET /reviveCoil/{coilId}改为POST /rollback/{currentCoilId} - 新增详细的回滚验证逻辑,包括检查数据类型、删除标志和发货状态 - 实现基于二维码记录的钢卷历史追踪,支持撤销更新、合卷和分卷操作 - 添加回滚操作的阻止机制,防止对初始创建、合卷产物和分卷产物进行回滚 - 完善回滚过程中的库位释放和数据恢复流程 - 更新二维码记录以记录回滚操作步骤和相关信息 --- .../controller/WmsMaterialCoilController.java | 34 +- .../klp/service/IWmsMaterialCoilService.java | 13 +- .../impl/WmsMaterialCoilServiceImpl.java | 363 +++++++++++++++--- 3 files changed, 335 insertions(+), 75 deletions(-) diff --git a/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java b/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java index e432dd2a..b41bb5b3 100644 --- a/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java +++ b/klp-wms/src/main/java/com/klp/controller/WmsMaterialCoilController.java @@ -301,30 +301,28 @@ public class WmsMaterialCoilController extends BaseController { } /** - * 复活历史钢卷 - * 将dataType=0的历史钢卷恢复为dataType=1的当前钢卷 - * 同时清空实际库位绑定,并检查当前钢卷号是否重复 + * 回滚钢卷操作 + * 根据当前钢卷ID找到历史钢卷并恢复,删除当前钢卷 + * 用于撤销单个更新、合卷或分卷操作 * - * @param coilId 钢卷ID - * @return 复活结果,包含success状态、错误信息、钢卷ID和当前钢卷号 + * @param currentCoilId 当前钢卷ID(需要回滚的钢卷) + * @return 操作结果,包含恢复的历史钢卷信息 */ @Log(title = "钢卷物料表", businessType = BusinessType.UPDATE) - @GetMapping("/reviveCoil/{coilId}") - public R> reviveCoil( - @NotNull(message = "钢卷ID不能为空") - @PathVariable("coilId") Long coilId) { + @RepeatSubmit() + @PostMapping("/rollback/{currentCoilId}") + public R> rollbackCoil( + @NotNull(message = "当前钢卷ID不能为空") + @PathVariable("currentCoilId") Long currentCoilId) { try { - Map result = iWmsMaterialCoilService.reviveCoil(coilId); - // 根据业务结果返回成功/失败的统一响应 - if (Boolean.TRUE.equals(result.get("success"))) { - return R.ok(result); - } else { - return R.fail(result.get("message").toString(), result); - } - } catch (Exception e) { - return R.fail("复活钢卷失败:" + e.getMessage()); + Map rollbackResult = iWmsMaterialCoilService.rollbackCoil(currentCoilId); + return R.ok(rollbackResult); + } catch (RuntimeException e) { + return R.fail("回滚失败:" + e.getMessage()); } } + + } diff --git a/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java b/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java index afc7e36a..aadb7f4d 100644 --- a/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java +++ b/klp-wms/src/main/java/com/klp/service/IWmsMaterialCoilService.java @@ -150,13 +150,14 @@ public interface IWmsMaterialCoilService { Map getDuplicateCoilGroups(); /** - * 复活历史钢卷 - * 将dataType=0的历史钢卷恢复为dataType=1的当前钢卷 - * 同时清空实际库位绑定,并检查当前钢卷号是否重复 + * 回滚钢卷操作 + * 根据当前钢卷ID找到历史钢卷并恢复,删除当前钢卷 + * 用于撤销单个更新、合卷或分卷操作 * - * @param coilId 钢卷ID - * @return 复活结果,包含success状态和错误信息 + * @param currentCoilId 当前钢卷ID(需要回滚的钢卷) + * @return 操作结果,包含恢复的历史钢卷信息 */ - Map reviveCoil(@NotNull(message = "钢卷ID不能为空") Long coilId); + Map rollbackCoil(@NotNull(message = "当前钢卷ID不能为空") Long currentCoilId); + } diff --git a/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java b/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java index 6f946ddb..51a0f368 100644 --- a/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java +++ b/klp-wms/src/main/java/com/klp/service/impl/WmsMaterialCoilServiceImpl.java @@ -2754,72 +2754,333 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService { } /** - * 复活历史钢卷 - * 将dataType=0的历史钢卷恢复为dataType=1的当前钢卷 - * 同时清空实际库位绑定,并检查当前钢卷号是否重复 + * 回滚钢卷操作 + * 根据当前钢卷ID找到历史钢卷并恢复,删除当前钢卷 + * 用于撤销单个更新、合卷或分卷操作 + * + * @param currentCoilId 当前钢卷ID(需要回滚的钢卷) + * @return 操作结果,包含恢复的历史钢卷信息 */ @Override @Transactional(rollbackFor = Exception.class) - public Map reviveCoil(Long coilId) { + public Map rollbackCoil(Long currentCoilId) { Map result = new HashMap<>(); result.put("success", false); - // 1. 查询钢卷信息 - WmsMaterialCoil coil = baseMapper.selectById(coilId); - if (coil == null) { - result.put("message", "钢卷不存在"); - return result; - } - - // 2. 检查是否为历史数据 - if (coil.getDataType() != 0) { - result.put("message", "该钢卷不是历史数据,无需复活"); - return result; - } - - // 3. 检查当前钢卷号是否重复(现存且未逻辑删除的) - if (StringUtils.isNotBlank(coil.getCurrentCoilNo())) { - LambdaQueryWrapper duplicateCheck = Wrappers.lambdaQuery(); - duplicateCheck.eq(WmsMaterialCoil::getCurrentCoilNo, coil.getCurrentCoilNo()) - .eq(WmsMaterialCoil::getDelFlag, 0) - .eq(WmsMaterialCoil::getDataType, 1) // 只检查当前数据 - .ne(WmsMaterialCoil::getCoilId, coilId); // 排除自身 - - long duplicateCount = baseMapper.selectCount(duplicateCheck); - if (duplicateCount > 0) { - result.put("message", "当前钢卷号[" + coil.getCurrentCoilNo() + "]已存在重复记录,请先删除错误钢卷后再复活"); - result.put("duplicateCoilNo", coil.getCurrentCoilNo()); - return result; - } - } - - // 4. 执行复活操作 try { - // 记录原实际库位ID,用于后续释放 - Long oldActualWarehouseId = coil.getActualWarehouseId(); - - // 更新钢卷状态:dataType=1,actualWarehouseId=null - LambdaUpdateWrapper updateWrapper = Wrappers.lambdaUpdate(); - updateWrapper.eq(WmsMaterialCoil::getCoilId, coilId) - .set(WmsMaterialCoil::getDataType, 1) // 恢复为当前数据 - .set(WmsMaterialCoil::getActualWarehouseId, (Long) null); // 清空实际库位绑定 - - int updateCount = baseMapper.update(null, updateWrapper); - if (updateCount <= 0) { - throw new RuntimeException("复活钢卷失败:数据库更新异常"); + // 1. 查询当前钢卷信息 + WmsMaterialCoil currentCoil = baseMapper.selectById(currentCoilId); + if (currentCoil == null) { + throw new RuntimeException("当前钢卷不存在"); } + // 2. 检查当前钢卷是否为当前数据(不能回滚历史数据) + if (currentCoil.getDataType() != 1) { + throw new RuntimeException("只能回滚当前数据,不能回滚历史数据"); + } + + if (!currentCoil.getDelFlag().equals(2)) { + throw new RuntimeException("当前钢卷已删除,无法回滚"); + } + + if (currentCoil.getStatus() == 1) { + throw new RuntimeException("只能回滚未发货数据,不能回滚已发货数据"); + } + + // 3. 检查是否有二维码记录 + if (currentCoil.getQrcodeRecordId() == null) { + throw new RuntimeException("当前钢卷没有二维码记录,无法回滚"); + } + + // 4. 查询二维码记录并解析上一步钢卷ID + WmsGenerateRecordVo qrcodeRecord = generateRecordService.queryById(currentCoil.getQrcodeRecordId()); + if (qrcodeRecord == null || StringUtils.isBlank(qrcodeRecord.getContent())) { + 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,无法回滚"); + } + } + + // 6. 查询历史钢卷信息 + WmsMaterialCoil historyCoil = baseMapper.selectById(historyCoilId); + if (historyCoil == null) { + throw new RuntimeException("历史钢卷不存在,无法回滚"); + } + + // 7. 检查历史钢卷是否为历史数据 + if (historyCoil.getDataType() != 0) { + throw new RuntimeException("目标钢卷不是历史数据,无法恢复"); + } + + // 8. 检查历史钢卷的钢卷号是否重复 + Map duplicateCheck = checkCoilNoDuplicate(null, historyCoil.getEnterCoilNo(), historyCoil.getCurrentCoilNo()); + String duplicateType = (String) duplicateCheck.get("duplicateType"); + if (!"none".equals(duplicateType)) { + String errorMsg = "无法恢复历史钢卷,存在重复的钢卷号:"; + if ("enter".equals(duplicateType)) { + errorMsg += "入场钢卷号[" + historyCoil.getEnterCoilNo() + "]重复"; + } else if ("current".equals(duplicateType)) { + errorMsg += "当前钢卷号[" + historyCoil.getCurrentCoilNo() + "]重复"; + } else if ("both".equals(duplicateType)) { + errorMsg += "入场钢卷号[" + historyCoil.getEnterCoilNo() + "]和当前钢卷号[" + 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); + + // 9.3 恢复历史钢卷为当前数据 + LambdaUpdateWrapper 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("message", "钢卷复活成功"); - result.put("coilId", coilId); - result.put("currentCoilNo", coil.getCurrentCoilNo()); + result.put("restoredCoil", restoredCoil); + result.put("deletedCoilId", currentCoilId); + result.put("message", "回滚操作成功,已恢复历史钢卷,删除了当前钢卷"); + + log.info("钢卷回滚操作成功:历史钢卷ID={}, 删除的钢卷ID={}", historyCoilId, currentCoilId); } catch (Exception e) { - log.error("复活钢卷失败,coilId: {}", coilId, e); - throw new RuntimeException("复活钢卷失败: " + e.getMessage()); + log.error("钢卷回滚操作失败:currentCoilId={}", currentCoilId, e); + result.put("success", false); + result.put("message", "回滚操作失败:" + e.getMessage()); + throw e; // 重新抛出异常,让事务回滚 } return result; } + /** + * 从二维码内容中解析历史钢卷ID + * 根据当前钢卷ID找到对应的上一步操作中的old_coil_id + */ + private Long parseHistoryCoilIdFromQrcode(String content, Long currentCoilId) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + Map contentMap = objectMapper.readValue(content, Map.class); + + @SuppressWarnings("unchecked") + List> steps = (List>) contentMap.get("steps"); + if (steps == null || steps.isEmpty()) { + return null; + } + + String currentCoilIdStr = currentCoilId.toString(); + + // 从后往前查找,找到当前钢卷ID对应的上一步操作 + for (int i = steps.size() - 1; i >= 0; i--) { + Map step = steps.get(i); + String action = (String) step.get("action"); + + // 如果是新增操作,说明已经回滚到最初状态 + if ("新增".equals(action)) { + return null; // 无法继续回滚 + } + + // 如果是回滚操作,跳过(回滚步骤不应该影响历史查找) + if ("回滚".equals(action)) { + continue; + } + + // 检查这个步骤是否产生了当前钢卷 + Object newCoilIdObj = step.get("new_coil_id"); + if (newCoilIdObj != null) { + String newCoilIdStr = newCoilIdObj.toString(); + if (newCoilIdStr.equals(currentCoilIdStr)) { + // 找到了产生当前钢卷的步骤,获取old_coil_id + Object oldCoilIdObj = step.get("old_coil_id"); + if (oldCoilIdObj != null) { + String oldCoilIdStr = oldCoilIdObj.toString(); + if (!"null".equals(oldCoilIdStr) && !oldCoilIdStr.isEmpty()) { + try { + return Long.parseLong(oldCoilIdStr); + } catch (NumberFormatException e) { + log.warn("解析old_coil_id失败:{}", oldCoilIdStr); + } + } + } + break; + } + } + + // 对于合卷操作,检查parent_coil_ids + if ("合卷".equals(step.get("operation"))) { + Object parentCoilIdsObj = step.get("parent_coil_ids"); + if (parentCoilIdsObj != null) { + String parentCoilIdsStr = parentCoilIdsObj.toString(); + if (parentCoilIdsStr.contains(currentCoilIdStr)) { + // 当前钢卷是合卷的产物,返回null表示无法继续回滚 + return null; + } + } + } + + // 对于分卷操作,检查new_current_coil_nos是否包含多个钢卷号 + if ("分卷".equals(step.get("operation"))) { + // 当前钢卷是分卷操作产生的子钢卷,无法回滚 + // 分卷操作会产生多个钢卷,回滚其中一个会导致其他钢卷异常 + return null; + } + } + + } catch (Exception e) { + log.error("解析二维码内容失败", e); + } + + return null; + } + + /** + * 获取无法回滚的具体原因 + * @return "INITIAL_CREATE" - 初始新增操作 + * "MERGE_PRODUCT" - 合卷产物 + * "SPLIT_PRODUCT" - 分卷产物 + * "UNKNOWN" - 其他未知原因 + */ + private String getRollbackBlockReason(String content, Long currentCoilId) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + Map contentMap = objectMapper.readValue(content, Map.class); + + @SuppressWarnings("unchecked") + List> steps = (List>) contentMap.get("steps"); + if (steps == null || steps.isEmpty()) { + return "UNKNOWN"; + } + + String currentCoilIdStr = currentCoilId.toString(); + + // 首先检查是否为初始新增操作 + Map firstStep = steps.get(0); + String action = (String) firstStep.get("action"); + if ("新增".equals(action)) { + Object coilIdObj = contentMap.get("coil_id"); + Object currentCoilIdObj = contentMap.get("current_coil_id"); + + if (coilIdObj != null && currentCoilIdObj != null) { + String coilIdStr = coilIdObj.toString(); + String currentCoilIdStrInContent = currentCoilIdObj.toString(); + + if (coilIdStr.equals(currentCoilIdStrInContent) && coilIdStr.equals(currentCoilIdStr)) { + return "INITIAL_CREATE"; + } + } + } + + // 检查是否为合卷或分卷的产物 + // 2. 从后往前遍历steps(最新操作优先),检查合卷/分卷产物 + // 倒序遍历,确保取到最新的操作状态 + for (int i = steps.size() - 1; i >= 0; i--) { + Map step = steps.get(i); + String operation = (String) step.get("operation"); + + // 检查合卷操作(优先判断,因为合卷可能是后续操作) + if ("合卷".equals(operation)) { + Object parentCoilIdsObj = step.get("parent_coil_ids"); + if (parentCoilIdsObj != null) { + String parentCoilIdsStr = parentCoilIdsObj.toString(); + // 校验当前钢卷ID是否在合卷的父卷列表中 + if (parentCoilIdsStr.contains(currentCoilIdStr)) { + return "MERGE_PRODUCT"; + } + } + } + + // 检查分卷操作 + if ("分卷".equals(operation)) { + return "SPLIT_PRODUCT"; + } + } + + return "UNKNOWN"; + } catch (Exception e) { + log.error("检查回滚阻止原因失败", e); + return "UNKNOWN"; + } + } + + /** + * 更新二维码记录以记录回滚操作 + */ + private void updateQrcodeForRollback(WmsGenerateRecordVo qrcodeRecord, WmsMaterialCoil currentCoil, WmsMaterialCoil historyCoil) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + Map contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class); + + // 获取现有steps + @SuppressWarnings("unchecked") + List> steps = (List>) contentMap.get("steps"); + if (steps == null) { + steps = new ArrayList<>(); + } + + // 添加回滚步骤 + Map 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("restored_coil_id", String.valueOf(historyCoil.getCoilId())); + rollbackStep.put("deleted_current_coil_no", currentCoil.getCurrentCoilNo()); + rollbackStep.put("restored_current_coil_no", historyCoil.getCurrentCoilNo()); + rollbackStep.put("operator", LoginHelper.getUsername()); + rollbackStep.put("rollback_time", new java.util.Date()); + + steps.add(rollbackStep); + contentMap.put("steps", steps); + + // 更新二维码中的当前钢卷信息指向恢复的历史钢卷 + contentMap.put("current_coil_id", String.valueOf(historyCoil.getCoilId())); + contentMap.put("current_coil_no", historyCoil.getCurrentCoilNo()); + // enter_coil_no 保持不变 + + // 更新二维码记录 + 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()); + } + } + + }