feat(wms): 添加钢卷单步分卷功能

- 在WmsMaterialCoil实体中新增exclusiveStatus字段用于标识独占状态
- 在WmsCoilPendingActionBo中新增remark字段用于存储操作备注
- 实现特殊分卷三步流程:startSpecialSplit锁定钢卷、createChildCoilInSpecialSplit创建子钢卷、completeSpecialSplit完成分卷
- 添加validateCoilOperationPermission方法验证钢卷操作权限防止并发冲突
- 在WmsMaterialCoilService中实现完整的特殊分卷业务逻辑
- 新增三个API接口分别对应特殊分卷的三个步骤
- 在查询条件中增加对exclusiveStatus字段的支持
- 完善错误处理和日志记录机制
This commit is contained in:
2026-01-22 10:23:30 +08:00
parent a82c8ea825
commit ef1d56dce3
9 changed files with 402 additions and 0 deletions

View File

@@ -323,6 +323,78 @@ public class WmsMaterialCoilController extends BaseController {
}
/**
* 特殊分卷 - 第一步:锁定钢卷
* 设置钢卷独占状态
*/
@Log(title = "钢卷物料表", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PostMapping("/specialSplit/start")
public R<Long> startSpecialSplit(
@NotNull(message = "钢卷ID不能为空")
@RequestParam Long coilId) {
try {
Boolean result = iWmsMaterialCoilService.startSpecialSplit(coilId);
if (Boolean.TRUE.equals(result)) {
return R.ok();
}
return R.fail("钢卷锁定失败");
} catch (RuntimeException e) {
return R.fail(e.getMessage());
}
}
/**
* 特殊分卷 - 第二步:逐个创建子钢卷
* 生成单个子钢卷并更新二维码信息
*/
@Log(title = "钢卷物料表", businessType = BusinessType.INSERT)
@RepeatSubmit()
@PostMapping("/specialSplit/createChild")
public R<WmsMaterialCoilVo> createChildCoilInSpecialSplit(
@NotNull(message = "母卷ID不能为空")
@RequestParam Long parentCoilId,
@NotNull(message = "待操作记录ID不能为空")
@RequestParam Long pendingActionId,
@RequestBody @NotNull(message = "子钢卷数据不能为空")
WmsMaterialCoilBo childCoilBo) {
try {
WmsMaterialCoilVo result =
iWmsMaterialCoilService.createChildCoilInSpecialSplit(
parentCoilId, childCoilBo, pendingActionId
);
return R.ok(result);
} catch (RuntimeException e) {
return R.fail("创建子钢卷失败:" + e.getMessage());
}
}
/**
* 特殊分卷 - 第三步:完成分卷操作
* 批量更新二维码追溯信息,设置母卷为历史数据,解除独占状态
*/
@Log(title = "钢卷物料表", businessType = BusinessType.UPDATE)
@RepeatSubmit()
@PostMapping("/specialSplit/complete")
public R<Map<String, Object>> completeSpecialSplit(
@NotNull(message = "待操作记录ID不能为空")
@RequestParam Long pendingActionId) {
try {
Map<String, Object> result =
iWmsMaterialCoilService.completeSpecialSplit(pendingActionId);
return R.ok(result);
} catch (RuntimeException e) {
return R.fail("完成分卷失败:" + e.getMessage());
}
}
}

View File

@@ -144,5 +144,10 @@ public class WmsMaterialCoil extends BaseEntity {
private String temperGrade;
// 镀层种类
private String coatingType;
/**
* 独占状态0=未独占1=特殊分卷中)
*/
private Integer exclusiveStatus;
}

View File

@@ -106,5 +106,8 @@ public class WmsCoilPendingActionBo extends BaseEntity {
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date endTime;
//备注
private String remark;
}

View File

@@ -221,5 +221,10 @@ public class WmsMaterialCoilBo extends BaseEntity {
private String temperGrade;
// 镀层种类
private String coatingType;
/**
* 独占状态0=未独占1=特殊分卷中)
*/
private Integer exclusiveStatus;
}

View File

@@ -328,5 +328,10 @@ public class WmsMaterialCoilVo extends BaseEntity {
private String temperGrade;
// 镀层种类
private String coatingType;
/**
* 独占状态0=未独占1=特殊分卷中)
*/
private Integer exclusiveStatus;
}

View File

@@ -159,5 +159,36 @@ public interface IWmsMaterialCoilService {
*/
Map<String, Object> rollbackCoil(@NotNull(message = "当前钢卷ID不能为空") Long currentCoilId);
/**
* 特殊分卷 - 第一步:锁定钢卷
* 设置钢卷独占状态
*
* @param coilId 被分卷的钢卷ID
* @return 待操作记录ID
*/
Boolean startSpecialSplit(@NotNull(message = "钢卷ID不能为空") Long coilId);
/**
* 特殊分卷 - 第二步:逐个创建子钢卷
* 生成单个子钢卷并更新二维码信息,不设置母卷为历史数据
*
* @param parentCoilId 母卷ID
* @param childCoilBo 子钢卷数据
* @param pendingActionId 待操作记录ID
* @return 创建的子钢卷信息
*/
WmsMaterialCoilVo createChildCoilInSpecialSplit(@NotNull(message = "母卷ID不能为空") Long parentCoilId,
@NotNull(message = "子钢卷数据不能为空") WmsMaterialCoilBo childCoilBo,
@NotNull(message = "待操作记录ID不能为空") Long pendingActionId);
/**
* 特殊分卷 - 第三步:完成分卷操作
* 批量更新二维码追溯信息,设置母卷为历史数据,解除独占状态
*
* @param pendingActionId 待操作记录ID
* @return 操作结果
*/
Map<String, Object> completeSpecialSplit(@NotNull(message = "待操作记录ID不能为空") Long pendingActionId);
}

View File

@@ -10,6 +10,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.klp.common.helper.LoginHelper;
import com.klp.domain.WmsMaterialCoil;
import com.klp.domain.vo.WmsMaterialCoilVo;
import com.klp.mapper.WmsMaterialCoilMapper;
import com.klp.service.IWmsMaterialCoilService;
import com.klp.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -35,6 +39,7 @@ public class WmsCoilPendingActionServiceImpl implements IWmsCoilPendingActionSer
private final WmsCoilPendingActionMapper baseMapper;
private final ISysUserService userService;
private final WmsMaterialCoilMapper materialCoilMapper;
/**
* 查询钢卷待操作
@@ -123,6 +128,12 @@ public class WmsCoilPendingActionServiceImpl implements IWmsCoilPendingActionSer
public Boolean insertByBo(WmsCoilPendingActionBo bo) {
WmsCoilPendingAction add = BeanUtil.toBean(bo, WmsCoilPendingAction.class);
validEntityBeforeSave(add);
if (add.getCoilId() != null){
WmsMaterialCoil materialCoil = materialCoilMapper.selectById(add.getCoilId());
if (materialCoil.getExclusiveStatus() !=0) {
throw new RuntimeException("该钢卷正在单步分条,请勿重复操作!");
}
}
// 设置默认值
if (add.getActionStatus() == null) {
add.setActionStatus(0); // 默认待处理

View File

@@ -19,6 +19,8 @@ import com.klp.common.utils.spring.SpringUtils;
import com.klp.domain.*;
import com.klp.domain.bo.*;
import com.klp.domain.vo.*;
import com.klp.domain.WmsCoilPendingAction;
import com.klp.domain.bo.WmsCoilPendingActionBo;
import com.klp.mapper.WmsDeliveryPlanMapper;
import com.klp.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
@@ -39,6 +41,7 @@ import com.klp.service.IWmsWarehouseService;
import com.klp.service.IWmsActualWarehouseService;
import com.klp.service.IWmsRawMaterialService;
import com.klp.service.IWmsBomItemService;
import com.klp.service.IWmsCoilPendingActionService;
import com.klp.service.IWmsProductService;
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -65,6 +68,7 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
private final IWmsActualWarehouseService actualWarehouseService;
private final IWmsRawMaterialService rawMaterialService;
private final IWmsBomItemService bomItemService;
private final IWmsCoilPendingActionService coilPendingActionService;
private final IWmsProductService productService;
private final ISysUserService userService;
private final WmsDeliveryPlanMapper deliveryPlanMapper;
@@ -350,6 +354,8 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
// 锌层种类和调制度
qw.eq(StringUtils.isNotBlank(bo.getCoatingType()), "mc.coating_type", bo.getCoatingType());
qw.eq(StringUtils.isNotBlank(bo.getTemperGrade()), "mc.temper_grade", bo.getTemperGrade());
// 独占状态
qw.eq(bo.getExclusiveStatus() != null, "mc.exclusive_status", bo.getExclusiveStatus());
// 统一处理 warehouseId 与 warehouseIds
List<Long> warehouseIdList = new ArrayList<>();
if (bo.getWarehouseId() != null) {
@@ -986,6 +992,9 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
throw new RuntimeException("钢卷ID不能为空");
}
// 检查独占状态
validateCoilOperationPermission(bo.getCoilId(), "简单更新");
// 查询原钢卷是否存在
WmsMaterialCoil oldCoil = baseMapper.selectById(bo.getCoilId());
if (oldCoil == null) {
@@ -1034,6 +1043,9 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
* 单个更新
*/
private Boolean updateBySingle(WmsMaterialCoilBo bo) {
// 检查独占状态
validateCoilOperationPermission(bo.getCoilId(), "单个更新");
// 查询原钢卷
WmsMaterialCoil oldCoil = baseMapper.selectById(bo.getCoilId());
if (oldCoil == null) {
@@ -1219,10 +1231,39 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
}
}
/**
* 检查钢卷是否被独占(用于单步分卷操作)
* @param coilId 钢卷ID
* @return true表示未被独占false表示被独占
*/
private boolean checkExclusiveStatus(Long coilId) {
if (coilId == null) {
return true;
}
WmsMaterialCoil coil = baseMapper.selectById(coilId);
return coil == null || coil.getExclusiveStatus() == null || coil.getExclusiveStatus() == 0;
}
/**
* 验证钢卷操作权限(检查独占状态)
* @param coilId 钢卷ID
* @param operation 操作名称(用于错误信息)
*/
private void validateCoilOperationPermission(Long coilId, String operation) {
if (!checkExclusiveStatus(coilId)) {
throw new RuntimeException("钢卷正在进行单步分卷操作中,无法执行" + operation + "操作");
}
}
/**
* 批量更新(分卷/合卷)
*/
private Boolean updateByBatch(WmsMaterialCoilBo bo) {
// 检查独占状态
if (bo.getCoilId() != null) {
validateCoilOperationPermission(bo.getCoilId(), "批量更新");
}
// 查询原钢卷(分卷时需要,合卷时可能不需要)
WmsMaterialCoil oldCoil = null;
if (bo.getCoilId() != null) {
@@ -1803,6 +1844,11 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
//TODO 做一些业务上的校验,判断是否需要校验
}
// 检查独占状态
for (Long id : ids) {
validateCoilOperationPermission(id, "删除");
}
// 获取要删除的钢卷记录
List<WmsMaterialCoil> coilList = baseMapper.selectBatchIds(ids);
if (coilList != null && !coilList.isEmpty()) {
@@ -2265,6 +2311,9 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
*/
@Override
public int exportCoil(Long coilId) {
// 检查独占状态
validateCoilOperationPermission(coilId, "发货");
// 查询当前钢卷信息记录原实际库区ID
WmsMaterialCoilVo wmsMaterialCoilVo = queryById(coilId);
Long oldActualWarehouseId = wmsMaterialCoilVo != null ? wmsMaterialCoilVo.getActualWarehouseId() : null;
@@ -2813,6 +2862,9 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
throw new RuntimeException("当前钢卷不存在");
}
// 检查独占状态
validateCoilOperationPermission(currentCoilId, "回滚");
// 2. 检查当前钢卷是否为当前数据(不能回滚历史数据)
if (currentCoil.getDataType() != 1) {
throw new RuntimeException("只能回滚当前数据,不能回滚历史数据");
@@ -3197,5 +3249,222 @@ public class WmsMaterialCoilServiceImpl implements IWmsMaterialCoilService {
}
}
/**
* 单步分卷 - 第一步:领料并锁定钢卷
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean startSpecialSplit(Long coilId) {
// 1. 查询钢卷信息
WmsMaterialCoil coil = baseMapper.selectById(coilId);
if (coil == null) {
throw new RuntimeException("钢卷不存在");
}
// 2. 检查钢卷状态
if (coil.getDataType() != 1) {
throw new RuntimeException("当前钢卷是历史卷");
}
// 3. 检查是否已被锁定
if (coil.getExclusiveStatus() != null && coil.getExclusiveStatus() != 0) {
throw new RuntimeException("钢卷正在进行其他操作,无法开始单步分卷");
}
// 4. 设置独占状态(锁定钢卷)
LambdaUpdateWrapper<WmsMaterialCoil> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(WmsMaterialCoil::getCoilId, coilId)
.set(WmsMaterialCoil::getExclusiveStatus, 1); // 1表示单步分卷中
baseMapper.update(null, updateWrapper);
return true;
}
/**
* 单步分卷 - 第二步:逐个创建子钢卷
*/
@Override
@Transactional(rollbackFor = Exception.class)
public WmsMaterialCoilVo createChildCoilInSpecialSplit(Long parentCoilId, WmsMaterialCoilBo childCoilBo, Long pendingActionId) {
// 1. 查询母卷信息
WmsMaterialCoil parentCoil = baseMapper.selectById(parentCoilId);
if (parentCoil == null) {
throw new RuntimeException("母卷不存在");
}
// 2. 验证独占状态
if (parentCoil.getExclusiveStatus() == null || parentCoil.getExclusiveStatus() != 1) {
throw new RuntimeException("母卷未处于单步分卷状态");
}
// 3. 查询待操作记录
WmsCoilPendingActionVo pendingAction = coilPendingActionService.queryById(pendingActionId);
if (pendingAction == null || !pendingAction.getCoilId().equals(parentCoilId)) {
throw new RuntimeException("待操作记录不存在或不匹配");
}
// 4. 检查子钢卷号是否重复
Map<String, Object> duplicateCheck = checkCoilNoDuplicate(null, parentCoil.getEnterCoilNo(), childCoilBo.getCurrentCoilNo());
if ("current".equals(duplicateCheck.get("duplicateType")) || "both".equals(duplicateCheck.get("duplicateType"))) {
throw new RuntimeException("子钢卷号[" + childCoilBo.getCurrentCoilNo() + "]已存在");
}
// 校验每个子卷的实际库位
if (childCoilBo.getActualWarehouseId() != null) {
validateActualWarehouseForAssign(childCoilBo.getActualWarehouseId(), null);
}
// 5. 创建子钢卷(参考普通分卷逻辑,但不设置母卷为历史数据)
WmsMaterialCoil childCoil = BeanUtil.toBean(childCoilBo, WmsMaterialCoil.class);
childCoil.setCoilId(null);
childCoil.setDataType(1); // 当前数据
childCoil.setEnterCoilNo(parentCoil.getEnterCoilNo());
childCoil.setSupplierCoilNo(parentCoil.getSupplierCoilNo());
childCoil.setExclusiveStatus(0); // 子卷不被锁定
// 继承母卷的基本信息
if (childCoil.getItemType() == null) {
childCoil.setItemType(parentCoil.getItemType());
}
if (childCoil.getItemId() == null) {
childCoil.setItemId(parentCoil.getItemId());
}
if (childCoil.getWarehouseId() == null) {
childCoil.setWarehouseId(parentCoil.getWarehouseId());
}
if (childCoil.getTeam() == null) {
childCoil.setTeam(parentCoil.getTeam());
}
// 在子卷的 parent_coil_nos 字段中记录母卷号
childCoil.setParentCoilNos(parentCoil.getCurrentCoilNo());
// 6. 生成子钢卷的二维码(复用分卷二维码生成逻辑)
List<String> allNewCoilNos = Arrays.asList(childCoilBo.getCurrentCoilNo());
Long childQrcodeId = generateQrcodeForSplit(parentCoil, childCoilBo, allNewCoilNos);
childCoil.setQrcodeRecordId(childQrcodeId);
// 7. 校验并插入子钢卷
validEntityBeforeSave(childCoil);
baseMapper.insert(childCoil);
if (childCoilBo.getActualWarehouseId() != null) {
updateActualWarehouseEnableStatus(childCoilBo.getActualWarehouseId(), null);
}
updateActualWarehouseEnableStatus(null, childCoilBo.getActualWarehouseId());
// 8. 更新二维码中的coilId
updateQrcodeCoilId(childQrcodeId, childCoil.getCoilId());
// 9. 更新待操作记录的备注字段记录已创建的子钢卷ID
String existingChildCoilIds = pendingAction.getRemark() != null ? pendingAction.getRemark() : "";
String updatedChildCoilIds = existingChildCoilIds.isEmpty() ?
String.valueOf(childCoil.getCoilId()) :
existingChildCoilIds + "," + childCoil.getCoilId();
WmsCoilPendingActionBo updatePendingBo = new WmsCoilPendingActionBo();
updatePendingBo.setActionId(pendingActionId);
updatePendingBo.setRemark(updatedChildCoilIds);
coilPendingActionService.updateByBo(updatePendingBo);
// 10. 返回创建的子钢卷信息
WmsMaterialCoilVo result = queryById(childCoil.getCoilId());
log.info("创建特殊分卷子钢卷母卷ID={}, 子钢卷ID={}, 待操作记录ID={}", parentCoilId, childCoil.getCoilId(), pendingActionId);
return result;
}
/**
* 单步分卷 - 第三步:完成分卷操作
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, Object> completeSpecialSplit(Long pendingActionId) {
Map<String, Object> result = new HashMap<>();
result.put("success", false);
try {
// 1. 查询待操作记录
WmsCoilPendingActionVo pendingAction = coilPendingActionService.queryById(pendingActionId);
if (pendingAction == null) {
throw new RuntimeException("待操作记录不存在");
}
if (pendingAction.getActionStatus() == 2) {
throw new RuntimeException("该分卷操作已完成");
}
// 2. 获取母卷和子卷信息
Long parentCoilId = pendingAction.getCoilId();
WmsMaterialCoil parentCoil = baseMapper.selectById(parentCoilId);
if (parentCoil == null) {
throw new RuntimeException("母卷不存在");
}
// 3. 获取所有子卷ID
String childCoilIdsStr = pendingAction.getRemark();
if (StringUtils.isBlank(childCoilIdsStr)) {
throw new RuntimeException("没有找到已创建的子钢卷,无法完成分卷");
}
List<Long> childCoilIds = Arrays.stream(childCoilIdsStr.split(","))
.map(String::trim)
.filter(StringUtils::isNotBlank)
.map(Long::parseLong)
.collect(Collectors.toList());
List<WmsMaterialCoil> childCoils = baseMapper.selectBatchIds(childCoilIds);
List<String> childCoilNos = childCoils.stream()
.map(WmsMaterialCoil::getCurrentCoilNo)
.collect(Collectors.toList());
// 4. 将母卷标记为历史数据
LambdaUpdateWrapper<WmsMaterialCoil> parentUpdateWrapper = new LambdaUpdateWrapper<>();
parentUpdateWrapper.eq(WmsMaterialCoil::getCoilId, parentCoilId)
.set(WmsMaterialCoil::getDataType, 0) // 历史数据
.set(WmsMaterialCoil::getParentCoilNos, String.join(",", childCoilNos)); // 记录子卷号
baseMapper.update(null, parentUpdateWrapper);
// 5. 批量更新所有子钢卷的二维码中的child_coil_ids
String allChildCoilIdsStr = childCoilIds.stream()
.map(String::valueOf)
.collect(Collectors.joining(","));
for (WmsMaterialCoil childCoil : childCoils) {
if (childCoil.getQrcodeRecordId() != null) {
updateChildCoilIdsQrcodeCoilId(childCoil.getQrcodeRecordId(), childCoil.getCoilId(), allChildCoilIdsStr);
}
}
// 6. 解除母卷的独占状态
LambdaUpdateWrapper<WmsMaterialCoil> unlockWrapper = new LambdaUpdateWrapper<>();
unlockWrapper.eq(WmsMaterialCoil::getCoilId, parentCoilId)
.set(WmsMaterialCoil::getExclusiveStatus, 0); // 解除锁定
baseMapper.update(null, unlockWrapper);
// 7. 更新待操作记录状态为已完成
WmsCoilPendingActionBo completePendingBo = new WmsCoilPendingActionBo();
completePendingBo.setActionId(pendingActionId);
completePendingBo.setActionStatus(2); // 2=已完成
completePendingBo.setCompleteTime(new Date());
coilPendingActionService.updateByBo(completePendingBo);
// 8. 返回结果
result.put("success", true);
result.put("parentCoilId", parentCoilId);
result.put("childCoilIds", childCoilIds);
result.put("message", "单步分卷操作已完成");
log.info("完成特殊分卷母卷ID={}, 子钢卷数量={}, 待操作记录ID={}", parentCoilId, childCoilIds.size(), pendingActionId);
} catch (Exception e) {
log.error("完成特殊分卷失败pendingActionId={}", pendingActionId, e);
result.put("success", false);
result.put("message", "完成分卷操作失败:" + e.getMessage());
throw e;
}
return result;
}
}

View File

@@ -100,6 +100,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
mc.length,
mc.coating_type,
mc.temper_grade,
mc.exclusive_status,
su.nick_name AS saleName,
aw.actual_warehouse_name AS actualWarehouseName,
CASE WHEN mc.item_type = 'raw_material' THEN rm.specification