feat(wms): 添加钢卷回滚功能替代原有的复活功能
- 将原有的reviveCoil方法重命名为rollbackCoil,实现更精确的回滚操作
- 修改接口路径从GET /reviveCoil/{coilId}改为POST /rollback/{currentCoilId}
- 新增详细的回滚验证逻辑,包括检查数据类型、删除标志和发货状态
- 实现基于二维码记录的钢卷历史追踪,支持撤销更新、合卷和分卷操作
- 添加回滚操作的阻止机制,防止对初始创建、合卷产物和分卷产物进行回滚
- 完善回滚过程中的库位释放和数据恢复流程
- 更新二维码记录以记录回滚操作步骤和相关信息
This commit is contained in:
@@ -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<Map<String, Object>> reviveCoil(
|
||||
@NotNull(message = "钢卷ID不能为空")
|
||||
@PathVariable("coilId") Long coilId) {
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/rollback/{currentCoilId}")
|
||||
public R<Map<String, Object>> rollbackCoil(
|
||||
@NotNull(message = "当前钢卷ID不能为空")
|
||||
@PathVariable("currentCoilId") Long currentCoilId) {
|
||||
try {
|
||||
Map<String, Object> 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<String, Object> rollbackResult = iWmsMaterialCoilService.rollbackCoil(currentCoilId);
|
||||
return R.ok(rollbackResult);
|
||||
} catch (RuntimeException e) {
|
||||
return R.fail("回滚失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -150,13 +150,14 @@ public interface IWmsMaterialCoilService {
|
||||
Map<String, Object> getDuplicateCoilGroups();
|
||||
|
||||
/**
|
||||
* 复活历史钢卷
|
||||
* 将dataType=0的历史钢卷恢复为dataType=1的当前钢卷
|
||||
* 同时清空实际库位绑定,并检查当前钢卷号是否重复
|
||||
* 回滚钢卷操作
|
||||
* 根据当前钢卷ID找到历史钢卷并恢复,删除当前钢卷
|
||||
* 用于撤销单个更新、合卷或分卷操作
|
||||
*
|
||||
* @param coilId 钢卷ID
|
||||
* @return 复活结果,包含success状态和错误信息
|
||||
* @param currentCoilId 当前钢卷ID(需要回滚的钢卷)
|
||||
* @return 操作结果,包含恢复的历史钢卷信息
|
||||
*/
|
||||
Map<String, Object> reviveCoil(@NotNull(message = "钢卷ID不能为空") Long coilId);
|
||||
Map<String, Object> rollbackCoil(@NotNull(message = "当前钢卷ID不能为空") Long currentCoilId);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String, Object> reviveCoil(Long coilId) {
|
||||
public Map<String, Object> rollbackCoil(Long currentCoilId) {
|
||||
Map<String, Object> 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<WmsMaterialCoil> 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<WmsMaterialCoil> 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<String, Object> 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<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("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<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()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String currentCoilIdStr = currentCoilId.toString();
|
||||
|
||||
// 从后往前查找,找到当前钢卷ID对应的上一步操作
|
||||
for (int i = steps.size() - 1; i >= 0; i--) {
|
||||
Map<String, Object> 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<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()) {
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
String currentCoilIdStr = currentCoilId.toString();
|
||||
|
||||
// 首先检查是否为初始新增操作
|
||||
Map<String, Object> 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<String, Object> 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<String, Object> contentMap = objectMapper.readValue(qrcodeRecord.getContent(), Map.class);
|
||||
|
||||
// 获取现有steps
|
||||
@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("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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user