箱条码与订单号的绑定与解绑注释

liweidong
绫Umbrella 1 month ago
parent e79bded7f2
commit 8891a55c0d
  1. 40
      blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/IOrderBoxService.java
  2. 221
      blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/IOrderBoxServiceImpl.java

@ -5,11 +5,47 @@ import org.springblade.desk.logistics.pojo.entity.BoxBinding;
import java.math.BigDecimal;
/**
* 订单箱管理服务接口
* 功能说明处理订单与箱条码的绑定解绑以及订单配件重量维护等核心物流业务逻辑
*
* @author 可补充作者名
* @date 可补充创建日期
*/
public interface IOrderBoxService {
/**
* 维护订单配件重量
* 功能根据卡号cardNo更新对应订单配件的实际重量信息
*
* @param cardNo 配件卡号/标识唯一标识需要更新重量的订单配件
* @param actualWeight 实际称重的重量值精度由业务场景决定如千克/
* @return R<?> 通用返回结果
* - 成功R.success()可携带更新成功的提示信息或数据
* - 失败R.fail()携带具体的失败原因如卡号不存在重量非法等
*/
R upholdOrderPartWeight(String cardNo, BigDecimal actualWeight);
/**
* 箱条码与订单绑定
* 功能将指定的箱条码与订单列表建立绑定关系核心物流绑定业务
*
* @param boxBinding 箱绑定实体类包含箱条码订单ID列表任务ID等核心绑定信息
* @return R<?> 通用返回结果
* - 成功R.success()携带绑定成功的提示或绑定记录数
* - 失败R.fail()携带失败原因如箱条码不存在订单已绑定唯一约束冲突等
*/
R boxBinding(BoxBinding boxBinding);
R boxOrderUnbind(String boxBarcode);
/**
* 箱条码与订单解绑
* 功能解除指定箱条码对应的所有订单绑定关系支持箱的重新绑定或回收
*
* @param boxBarcode 箱条码唯一标识需要解绑的箱子
* @return R<?> 通用返回结果
* - 成功R.success()携带解绑成功的提示或解绑的订单数量
* - 失败R.fail()携带失败原因如箱条码不存在解绑数据异常等
*/
R boxOrderUnbind(String boxBarcode);
}
}

@ -20,7 +20,8 @@ import static org.springblade.desk.logistics.pojo.entity.Station.STATUS_OCCUPIED
import static org.springblade.desk.logistics.pojo.entity.Task.STATUS_FINISHED;
/**
* 订单箱子 控制器
* 订单箱业务实现类
* 核心职责处理订单与箱条码的绑定/解绑订单配件重量维护任务/站点/库位状态管理等物流核心业务
*
* @author qyl
* @since 2026-01-08
@ -28,26 +29,54 @@ import static org.springblade.desk.logistics.pojo.entity.Task.STATUS_FINISHED;
@Service
@Slf4j
public class IOrderBoxServiceImpl implements IOrderBoxService {
/**
* 工单服务处理工单重量订单信息查询等
*/
private final IYieldOrderService iYieldOrderService;
/**
* 任务服务处理箱绑定任务的创建状态更新删除等
*/
private final ITaskService iTaskService;
/**
* 订单绑定服务处理订单与任务的绑定关系维护
*/
private final IOrderBindService iOrderBindService;
/**
* 站点服务处理站点状态占用/空闲管理
*/
private final IStationService iStationService;
/**
* 库位服务处理库位状态占用/空闲管理
*/
private final ILocationService iLocationService;
/**
* 任务运行中状态集合包含任务从启动到待入库的所有中间状态
* 用于判断箱条码是否存在未完成的运行任务
*/
private static final Set<Integer> RUNNING_STATUSES = new HashSet<>();
static {
RUNNING_STATUSES.add(Task.STATUS_START);
RUNNING_STATUSES.add(Task.STATUS_CONVEYOR_START);
RUNNING_STATUSES.add(Task.STATUS_CONVEYOR_END);
RUNNING_STATUSES.add(Task.STATUS_STATION);
RUNNING_STATUSES.add(Task.STATUS_LOCATION);
RUNNING_STATUSES.add(Task.STATUS_WAITING);
RUNNING_STATUSES.add(Task.STATUS_STATION_RECEIVE);
RUNNING_STATUSES.add(Task.STATUS_BACK_TO_STORAGE);
// 初始化运行中任务状态
RUNNING_STATUSES.add(Task.STATUS_START); // 任务启动
RUNNING_STATUSES.add(Task.STATUS_CONVEYOR_START); // 输送机启动
RUNNING_STATUSES.add(Task.STATUS_CONVEYOR_END); // 输送机结束
RUNNING_STATUSES.add(Task.STATUS_STATION); // 站点状态
RUNNING_STATUSES.add(Task.STATUS_LOCATION); // 库位状态
RUNNING_STATUSES.add(Task.STATUS_WAITING); // 等待状态
RUNNING_STATUSES.add(Task.STATUS_STATION_RECEIVE);// 站点接收
RUNNING_STATUSES.add(Task.STATUS_BACK_TO_STORAGE);// 返库状态
}
/**
* 构造器注入依赖服务替代@Autowired符合Spring最佳实践
*
* @param iYieldOrderService 工单服务
* @param iTaskService 任务服务
* @param iOrderBindService 订单绑定服务
* @param iStationService 站点服务
* @param iLocationService 库位服务
*/
public IOrderBoxServiceImpl(IYieldOrderService iYieldOrderService, ITaskService iTaskService, IOrderBindService iOrderBindService, IStationService iStationService, ILocationService iLocationService) {
this.iYieldOrderService = iYieldOrderService;
this.iTaskService = iTaskService;
@ -56,18 +85,38 @@ public class IOrderBoxServiceImpl implements IOrderBoxService {
this.iLocationService = iLocationService;
}
/**
* 维护订单配件实际重量
* 功能根据流程卡号更新对应工单的实际称重数据
*
* @param cardNo 流程卡号唯一标识工单
* @param actualWeight 实际称重值单位业务约定如千克
* @return R<?> 操作结果
* - 成功R.success()
* - 失败R.fail()携带具体失败原因
*/
@Override
public R upholdOrderPartWeight(String cardNo, BigDecimal actualWeight) {
log.info("接收到实际重量:{},对应的流程卡号:{}",actualWeight,cardNo);
//获取流程卡号
// 根据流程卡号查询工单(按更新时间倒序,取最新记录)
List<YieldOrder> list = iYieldOrderService.list(new QueryWrapper<YieldOrder>().eq("CARD_NO", cardNo).orderByDesc("UPDATE_TIME"));
//修改重量
// 更新最新工单的实际重量
list.get(0).setActualWeighing(actualWeight);
// 执行更新并返回结果
return iYieldOrderService.updateById(list.get(0))? R.success():R.fail("实际称重维护:卡号维护失败");
}
/**
* 箱条码与订单绑定核心方法
* 流程参数校验 任务状态校验 订单绑定状态校验 站点/库位分配 任务创建 订单绑定
*
* @param boxBinding 箱绑定参数包含箱条码订单ID列表工位ID等
* @return R<?> 绑定结果
* - 成功R.success()
* - 失败R.fail()携带具体失败原因如参数为空订单已绑定重量超限等
*/
@Override
public R boxBinding(BoxBinding boxBinding) {
log.info("接收到箱绑定实际参数:{}", boxBinding);
@ -79,78 +128,109 @@ public class IOrderBoxServiceImpl implements IOrderBoxService {
}
String boxBarcode = boxBinding.getBoxBarcode();
// 2. 校验箱条码是否存在运行中的任务(封装为独立方法,提升可读性
// 2. 校验箱条码是否存在运行中的任务(避免重复绑定
R taskCheckResult = checkBoxBarcodeRunningTask(boxBarcode);
if (!taskCheckResult.isSuccess()) {
return taskCheckResult;
}
// 3. 校验订单是否已绑定(封装为独立方法
// 3. 校验订单是否已绑定(避免订单重复绑定
R orderCheckResult = checkOrderIdBoundStatus(boxBinding.getOrderIdList());
if (!orderCheckResult.isSuccess()) {
return orderCheckResult;
}
// 4. 构建任务基础信息
Long wcId = boxBinding.getWcId();
Task task = new Task();
task.setBoxBarcode(boxBinding.getBoxBarcode());
task.setBoxBarcode(boxBarcode);
task.setWcId(wcId);
// 4. 获取当前站点位置
// 5. 获取当前可用的站点/库位并分配
R location = getSiteLocation(task);
if (!location.isSuccess()) {
return location;
}
// 5. 保存记录到数据库中
// 6. 完善任务信息并保存
task= (Task) location.getData();
task.setTaskStatus(Task.STATUS_START);
task.setCreateTime(new Date());
task.setTaskStatus(Task.STATUS_START); // 设置任务初始状态为启动
task.setCreateTime(new Date()); // 设置任务创建时间
// 7. 计算订单总重量(无订单则重量为0)
boolean orderBool = boxBinding.getOrderIdList() == null || boxBinding.getOrderIdList().size() == 0;
if (orderBool) {
task.setWeight(new BigDecimal(0));
}else {
task.setWeight(getWeightByOrderIdList(boxBinding.getOrderIdList()));
}
// 8. 重量校验(小于50kg才允许绑定,避免超重)
if (task.getWeight().compareTo(BigDecimal.valueOf(50)) < 0) {
R.fail("箱条码绑定的订单重量过重,请重新进行绑定");
return R.fail("箱条码绑定的订单重量过重,请重新进行绑定");
}
// 9. 保存任务记录
if (!iTaskService.save(task)) {
R.fail("保存绑定箱条码异常");
return R.fail("保存绑定箱条码异常");
}
// 10. 无订单则直接返回成功,有订单则执行订单绑定
if (orderBool) {
return R.success();
}else {
return saveOrderBindingList(task.getId(),boxBinding.getOrderIdList());
}
}
/**
* 箱条码与订单解绑
* 流程参数校验 查询运行中任务 重置站点/库位状态 解绑订单 结束任务
*
* @param boxBarcode 箱条码
* @return R<?> 解绑结果
* - 成功R.success()
* - 失败R.fail()携带具体失败原因如箱条码为空
*/
@Override
public R boxOrderUnbind(String boxBarcode) {
// 1. 参数非空校验
if (boxBarcode.isEmpty()) {
log.warn("箱绑定参数为空或箱条码缺失");
return R.fail("箱条码不能为空");
}
// 1. 根据箱条码查询数据
// 2. 查询该箱条码对应的运行中任务
List<Task> taskList = iTaskService.list(
new LambdaQueryWrapper<Task>().eq(Task::getBoxBarcode, boxBarcode).in(Task::getTaskStatus,RUNNING_STATUSES)
);
// 3. 遍历任务,重置站点/库位状态 + 解绑订单 + 结束任务
for (Task task : taskList) {
// 3.1 重置站点状态为占用(释放站点)
if (task.getStationId()!=null&&task.getStationId()!=0) {
Station station = iStationService.getById(task.getStationId());
station.setStationStatus(STATUS_OCCUPIED);
iStationService.updateById(station);
}
// 3.2 重置库位状态为占用(释放库位)
if (task.getLocationId()!=null&&task.getLocationId()!=0) {
Location location = iLocationService.getById(task.getLocationId());
location.setLocationStatus(STATUS_OCCUPIED);
iLocationService.updateById(location);
}
// 3.3 解绑订单(更新绑定状态为未绑定)
List<OrderBind> orderBindList = iOrderBindService.list(new LambdaQueryWrapper<OrderBind>().eq(OrderBind::getTaskId, task.getId()));
if (orderBindList!=null&&orderBindList.size()!=0) {
if (!CollectionUtils.isEmpty(orderBindList)) {
for (OrderBind orderBind : orderBindList) {
orderBind.setBindingStatus(STATUS_UNBINDED);
iOrderBindService.updateById(orderBind);
}
}
// 3.4 结束任务(更新任务状态为已完成)
task.setTaskStatus(STATUS_FINISHED);
iTaskService.updateById(task);
}
@ -158,56 +238,101 @@ public class IOrderBoxServiceImpl implements IOrderBoxService {
return R.success();
}
/**
* 批量保存订单与任务的绑定关系
* 核心创建订单绑定记录并批量插入插入失败则回滚任务删除已创建的任务
*
* @param taskId 任务ID关联箱条码
* @param orderIdList 订单ID列表
* @return R<?> 保存结果
* - 成功R.success()
* - 失败R.fail()删除任务并返回异常信息
*/
private R saveOrderBindingList(Long taskId, ArrayList<Long> orderIdList) {
ArrayList<OrderBind> orderBindList = new ArrayList<>();
// 构建订单绑定记录
for (Long orderId : orderIdList) {
OrderBind orderBind = new OrderBind();
orderBind.setBindingStatus(OrderBind.STATUS_BOUND);
orderBind.setTaskId(taskId);
orderBind.setOrderId(orderId);
orderBind.setBindingStatus(OrderBind.STATUS_BOUND); // 绑定状态:已绑定
orderBind.setTaskId(taskId); // 关联任务ID
orderBind.setOrderId(orderId); // 关联订单ID
orderBindList.add(orderBind);
}
// 批量插入绑定记录,失败则删除任务
if (iOrderBindService.saveBatch(orderBindList)) {
return R.success();
}else {
iTaskService.removeById(taskId);
iTaskService.removeById(taskId); // 回滚:删除已创建的任务
return R.fail("订单绑定箱条码异常");
}
}
/**
* 根据订单ID列表计算订单总重量
* 逻辑查询订单对应的工单 累加实际称重值过滤null值
*
* @param orderIdList 订单ID列表
* @return BigDecimal 订单总重量单位业务约定如千克
*/
private BigDecimal getWeightByOrderIdList(ArrayList<Long> orderIdList) {
// 查询订单对应的工单列表
List<YieldOrder> orderList = iYieldOrderService.list(new LambdaQueryWrapper<YieldOrder>().in(YieldOrder::getId, orderIdList));
// 流式累加实际称重值(过滤null,初始值为0)
return orderList.stream()
.map(YieldOrder::getActualWeighing)
.filter(Objects::nonNull)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
/**
* 获取当前工位可用的站点/库位并分配
* 优先级先分配站点 站点无可用则分配库位 均无则返回失败
*
* @param task 任务对象包含工位ID
* @return R<?> 分配结果
* - 成功R.data(task)任务对象已填充站点/库位ID
* - 失败R.fail()提示库位繁忙
*/
private R getSiteLocation(Task task) {
// 1. 查询当前工位可用的站点(状态为占用的站点)
List<Station> list = iStationService.list(new LambdaQueryWrapper<Station>().eq(Station::getWcId, task.getWcId()).eq(Station::getStationStatus, STATUS_OCCUPIED));
if (list!=null&&list.size()!=0) {
task.setStationId(list.get(0).getId());
if (!CollectionUtils.isEmpty(list)) {
task.setStationId(list.get(0).getId()); // 分配第一个可用站点
// 更新站点状态为预占用(锁定站点)
Station station = list.get(0);
station.setStationStatus(Station.PRE_STATUS_OCCUPIED);
iStationService.updateById(station);
return R.data(task);
}
// 2. 站点无可用则查询可用库位(状态为占用的库位)
List<Location> locationList = iLocationService.list(new LambdaQueryWrapper<Location>().eq(Location::getLocationStatus, Location.STATUS_OCCUPIED));
if (locationList!=null&&locationList.size()!=0) {
task.setLocationId(locationList.get(0).getId());
if (!CollectionUtils.isEmpty(locationList)) {
task.setLocationId(locationList.get(0).getId()); // 分配第一个可用库位
// 更新库位状态为空闲(锁定库位)
Location location = locationList.get(0);
location.setLocationStatus(Location.STATUS_FREE);
iLocationService.updateById(location);
return R.data(task);
}
// 3. 站点/库位均无可用
return R.fail("当前班次库位繁忙,请空闲后再试");
}
/**
* 校验箱条码是否存在运行中的任务
* 核心避免同一箱条码同时存在多个未完成的绑定任务
*
* @param boxBarcode 箱条码
* @return R<?> 校验结果
* - 成功R.success()无运行中任务
* - 失败R.fail()存在运行中任务
*/
private R checkBoxBarcodeRunningTask(String boxBarcode) {
// 查询箱条码对应的任务(MyBatis-Plus的list方法返回空列表,非null,无需判null)
// 查询箱条码对应的所有任务
List<Task> taskList = iTaskService.list(
new LambdaQueryWrapper<Task>().eq(Task::getBoxBarcode, boxBarcode)
);
@ -217,10 +342,10 @@ public class IOrderBoxServiceImpl implements IOrderBoxService {
return R.success();
}
// 判断是否有运行中的任务(流式操作简化,无冗余map
// 判断是否有运行中的任务(流式操作,简化逻辑
boolean hasRunningTask = taskList.stream()
.map(Task::getTaskStatus)
.filter(status -> status != null) // 过滤null状态,避免NPE
.filter(status -> status != null) // 过滤null状态,避免空指针
.anyMatch(RUNNING_STATUSES::contains);
if (hasRunningTask) {
@ -230,9 +355,14 @@ public class IOrderBoxServiceImpl implements IOrderBoxService {
return R.success();
}
/**
* 校验订单号是否已绑定核心优化减少遍历次数流式收集数据
* 校验订单是否已绑定
* 核心避免同一订单被重复绑定到不同箱条码
*
* @param orderIdList 订单ID列表
* @return R<?> 校验结果
* - 成功R.success()无已绑定订单
* - 失败R.fail()存在已绑定订单返回对应流程卡号
*/
private R checkOrderIdBoundStatus(List<Long> orderIdList) {
// 无订单号,直接通过校验
@ -240,7 +370,7 @@ public class IOrderBoxServiceImpl implements IOrderBoxService {
return R.success();
}
// 查询订单绑定记录
// 查询订单对应的绑定记录
List<OrderBind> orderBindList = iOrderBindService.list(
new LambdaQueryWrapper<OrderBind>().in(OrderBind::getOrderId, orderIdList)
);
@ -248,12 +378,12 @@ public class IOrderBoxServiceImpl implements IOrderBoxService {
return R.success();
}
// 步骤1:过滤出已绑定的订单ID(流式收集,替代循环
// 过滤出已绑定的订单ID(去重,避免重复提示
List<Long> boundOrderIds = orderBindList.stream()
.filter(orderBind -> OrderBind.STATUS_BOUND.equals(orderBind.getBindingStatus()))
.map(OrderBind::getOrderId)
.filter(orderId -> orderId != null) // 过滤null订单ID
.distinct() // 去重,避免重复查询
.filter(orderId -> orderId != null)
.distinct()
.collect(Collectors.toList());
// 无已绑定订单,直接通过
@ -261,17 +391,16 @@ public class IOrderBoxServiceImpl implements IOrderBoxService {
return R.success();
}
// 步骤2:查询已绑定订单对应的流程卡号(流式收集,替代循环)
// 查询已绑定订单对应的流程卡号,构造异常信息
List<String> boundCardNos = iYieldOrderService.list(
new LambdaQueryWrapper<YieldOrder>().in(YieldOrder::getId, boundOrderIds)
).stream()
.map(YieldOrder::getCardNo)
.filter(cardNo -> cardNo != null) // 过滤null卡号
.filter(cardNo -> cardNo != null)
.collect(Collectors.toList());
// 构造异常信息并返回
String errorMsg = String.format("以下流程卡号处于绑定状态,绑定异常:{%s}", String.join(",", boundCardNos));
log.warn(errorMsg);
return R.fail(errorMsg);
}
}
}
Loading…
Cancel
Save