From d893b06989452a80df43ab2738c21534a69e5cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BB=ABUmbrella?= <2539020564@qq.com> Date: Fri, 6 Mar 2026 19:10:09 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9B=9E=E5=BA=93=E4=BB=A3=E7=A0=81=E7=BC=96?= =?UTF-8?q?=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pojo/dto/ReturnToWarehouseDto.java | 21 ++ .../desk/logistics/pojo/entity/Station.java | 4 + .../logistics/pojo/vo/BsWorkCenterVO.java | 12 + .../dashboard/mapper/BsWorkCenterMapper.java | 7 + .../dashboard/mapper/BsWorkCenterMapper.xml | 16 + .../service/IBsWorkCenterService.java | 2 + .../service/impl/BsWorkCenterServiceImpl.java | 5 + .../controller/OrderBoxController.java | 21 ++ .../logistics/service/IOrderBoxService.java | 17 +- .../service/IPipelineOrderBoxService.java | 18 + .../service/impl/IOrderBoxServiceImpl.java | 331 ++++++++++++++---- .../impl/IPipelineOrderBoxServiceImpl.java | 123 +++++++ .../service/impl/PipelineServiceImpl.java | 6 +- .../desk/logistics/utils/AgvTaskTypeUtil.java | 2 +- .../logistics/utils/CollectionCheckUtil.java | 36 ++ 15 files changed, 539 insertions(+), 82 deletions(-) create mode 100644 blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/dto/ReturnToWarehouseDto.java create mode 100644 blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/vo/BsWorkCenterVO.java create mode 100644 blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/mapper/BsWorkCenterMapper.xml create mode 100644 blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/IPipelineOrderBoxService.java create mode 100644 blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/IPipelineOrderBoxServiceImpl.java create mode 100644 blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/utils/CollectionCheckUtil.java diff --git a/blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/dto/ReturnToWarehouseDto.java b/blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/dto/ReturnToWarehouseDto.java new file mode 100644 index 00000000..71489fbd --- /dev/null +++ b/blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/dto/ReturnToWarehouseDto.java @@ -0,0 +1,21 @@ +package org.springblade.desk.logistics.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Data +@Schema(description = "出库操作") +public class ReturnToWarehouseDto { + + //箱条码 + @NotBlank(message = "箱条码不能为空") + private String boxBarcode; + //站点id + @NotBlank(message = "站点编号不能为空") + private String stationCode; + //终点id + private Long endLocationId ; + //终点名称 + private String endName; +} diff --git a/blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/entity/Station.java b/blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/entity/Station.java index 892bdadc..c0f14fe2 100644 --- a/blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/entity/Station.java +++ b/blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/entity/Station.java @@ -46,6 +46,10 @@ public class Station extends BaseEntity { * 放货输送线 站点类型常量 */ public static final String DROPOFF_CONVEYOR_LINE = "1001"; + /** + * 放货输送线 站点类型常量 + */ + public static final String DROPOFF_CONVEYOR_LINE_NAME = "输送线终点"; /** diff --git a/blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/vo/BsWorkCenterVO.java b/blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/vo/BsWorkCenterVO.java new file mode 100644 index 00000000..51ff66ac --- /dev/null +++ b/blade-service-api/blade-desk-api/src/main/java/org/springblade/desk/logistics/pojo/vo/BsWorkCenterVO.java @@ -0,0 +1,12 @@ +package org.springblade.desk.logistics.pojo.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "wslist返回对象") +public class BsWorkCenterVO { + private Long id; + private String wcName; + +} diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/mapper/BsWorkCenterMapper.java b/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/mapper/BsWorkCenterMapper.java index bb8370ca..bfd8c50f 100644 --- a/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/mapper/BsWorkCenterMapper.java +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/mapper/BsWorkCenterMapper.java @@ -63,4 +63,11 @@ public interface BsWorkCenterMapper extends BaseMapper { List getList(); BsWorkCenterEntity selectBsWorkCenterByWcCode(@Param("wcCode")String wcCode); + + /** + * 根据ID集合查询工作中心 + * @param ids ID集合 + * @return 工作中心列表 + */ + List selectByIds(@Param("ids") List ids); } diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/mapper/BsWorkCenterMapper.xml b/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/mapper/BsWorkCenterMapper.xml new file mode 100644 index 00000000..6021cbd9 --- /dev/null +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/mapper/BsWorkCenterMapper.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/service/IBsWorkCenterService.java b/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/service/IBsWorkCenterService.java index cf1dd8ac..2226e034 100644 --- a/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/service/IBsWorkCenterService.java +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/service/IBsWorkCenterService.java @@ -70,4 +70,6 @@ public interface IBsWorkCenterService extends BaseService { * @return */ BsWorkCenterEntity selectBsWorkCenterByWcCode(String wcCode); + + List getByIds(List list); } diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/service/impl/BsWorkCenterServiceImpl.java b/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/service/impl/BsWorkCenterServiceImpl.java index 1379628e..0051f33c 100644 --- a/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/service/impl/BsWorkCenterServiceImpl.java +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/dashboard/service/impl/BsWorkCenterServiceImpl.java @@ -74,4 +74,9 @@ public class BsWorkCenterServiceImpl extends BaseServiceImpl getByIds(List list) { + return workCenterMapper.selectByIds(list); + } + } diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/controller/OrderBoxController.java b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/controller/OrderBoxController.java index 37acc733..86727624 100644 --- a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/controller/OrderBoxController.java +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/controller/OrderBoxController.java @@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; +import jodd.util.StringUtil; import lombok.AllArgsConstructor; import org.springblade.core.boot.ctrl.BladeController; import org.springblade.core.mp.support.Condition; @@ -18,6 +19,7 @@ import org.springblade.desk.dashboard.service.IBsWorkCenterService; import org.springblade.desk.device.pojo.entity.EquipmentEntity; import org.springblade.desk.device.pojo.vo.EquipmentVO; import org.springblade.desk.logistics.pojo.dto.BoxBindingDto; +import org.springblade.desk.logistics.pojo.dto.ReturnToWarehouseDto; import org.springblade.desk.logistics.pojo.dto.TaskDto; import org.springblade.desk.logistics.pojo.vo.TaskVO; import org.springblade.desk.logistics.service.IOrderBoxService; @@ -110,5 +112,24 @@ public class OrderBoxController extends BladeController { IPage pages = taskService.selectPage(Condition.getPage(query), taskDto); return R.data(pages); } + + /** + * 回库 + */ + @PostMapping("/return-warehouse") + @ApiOperationSupport(order = 5) + @Operation(summary = "回库", description = "调用回库") + public R inventoryReturnToWarehouse(@RequestParam ReturnToWarehouseDto returnToWarehouseDto){ + return iOrderBoxService.inventoryReturnToWarehouse(returnToWarehouseDto); + } + /** + * 回调作业中心list + */ + @PostMapping("/return-warehouse-list") + @ApiOperationSupport(order = 6) + @Operation(summary = "回调作业中心list", description = "回调作业中心list") + public R inventoryReturnToWarehouseList(){ + return iOrderBoxService.returnToWarehouseList(); + } } diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/IOrderBoxService.java b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/IOrderBoxService.java index 4af6129e..de6e2975 100644 --- a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/IOrderBoxService.java +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/IOrderBoxService.java @@ -2,6 +2,7 @@ package org.springblade.desk.logistics.service; import org.springblade.core.tool.api.R; import org.springblade.desk.logistics.pojo.dto.BoxBindingDto; +import org.springblade.desk.logistics.pojo.dto.ReturnToWarehouseDto; import java.math.BigDecimal; @@ -37,15 +38,15 @@ public interface IOrderBoxService { */ R boxBinding(BoxBindingDto boxBinding); + /** - * 箱条码与订单解绑 - * 功能:解除指定箱条码对应的所有订单绑定关系,支持箱的重新绑定或回收 - * - * @param boxBarcode 箱条码,唯一标识需要解绑的箱子 - * @return R 通用返回结果 - * - 成功:R.success(),携带解绑成功的提示或解绑的订单数量 - * - 失败:R.fail(),携带失败原因(如箱条码不存在、解绑数据异常等) + * 释放站点并调用AGV小车接口(优化后的核心方法) + * 业务场景:任务结束后释放站点,触发AGV小车相关操作 + * @param ReturnToWarehouseDto returnToWarehouseDto + * @return 统一返回结果 */ - R boxOrderUnbind(String boxBarcode); + R inventoryReturnToWarehouse(ReturnToWarehouseDto returnToWarehouseDto); + + R returnToWarehouseList(); } \ No newline at end of file diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/IPipelineOrderBoxService.java b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/IPipelineOrderBoxService.java new file mode 100644 index 00000000..20f8fa2e --- /dev/null +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/IPipelineOrderBoxService.java @@ -0,0 +1,18 @@ +package org.springblade.desk.logistics.service; + +import org.springblade.core.tool.api.R; +import org.springblade.desk.logistics.pojo.entity.Task; + +public interface IPipelineOrderBoxService { + /** + * 箱条码与订单解绑 + * 功能:解除指定箱条码对应的所有订单绑定关系,支持箱的重新绑定或回收 + * + * @param boxBarcode 箱条码,唯一标识需要解绑的箱子 + * @return R 通用返回结果 + * - 成功:R.success(),携带解绑成功的提示或解绑的订单数量 + * - 失败:R.fail(),携带失败原因(如箱条码不存在、解绑数据异常等) + */ + R boxOrderUnbind(String boxBarcode); + +} diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/IOrderBoxServiceImpl.java b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/IOrderBoxServiceImpl.java index 0abe20fc..b98a23a1 100644 --- a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/IOrderBoxServiceImpl.java +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/IOrderBoxServiceImpl.java @@ -2,23 +2,32 @@ package org.springblade.desk.logistics.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import groovy.lang.Lazy; import lombok.extern.slf4j.Slf4j; import org.springblade.core.secure.utils.AuthUtil; import org.springblade.core.tool.api.R; +import org.springblade.desk.dashboard.service.IBsWorkCenterService; import org.springblade.desk.logistics.pojo.dto.BoxBindingDto; +import org.springblade.desk.logistics.pojo.dto.ReturnToWarehouseDto; import org.springblade.desk.logistics.pojo.entity.*; +import org.springblade.desk.logistics.pojo.vo.BsWorkCenterVO; import org.springblade.desk.logistics.service.*; +import org.springblade.desk.logistics.utils.AgvTaskTypeUtil; import org.springblade.desk.order.pojo.entity.YieldOrder; import org.springblade.desk.order.service.IYieldOrderService; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import java.math.BigDecimal; import java.util.*; import java.util.stream.Collectors; +import static org.springblade.desk.logistics.constant.AgvConstant.EQUIPMENT_TYPE_AGV; import static org.springblade.desk.logistics.pojo.entity.OrderBind.STATUS_UNBINDED; -import static org.springblade.desk.logistics.pojo.entity.Station.STATUS_OCCUPIED; +import static org.springblade.desk.logistics.pojo.entity.Station.*; +import static org.springblade.desk.logistics.utils.CollectionCheckUtil.isFieldInCollection; /** * 订单箱业务实现类 @@ -51,6 +60,11 @@ public class IOrderBoxServiceImpl implements IOrderBoxService { */ private final ILocationService iLocationService; + + private final AgvTaskTypeUtil agvTaskTypeUtil; + private final IBsWorkCenterService bsWorkCenterService; + private final IPipelineService iPipelineOrderBoxService; + /** * 任务运行中状态集合:包含任务从启动到待入库的所有中间状态 * 用于判断箱条码是否存在未完成的运行任务 @@ -69,36 +83,41 @@ public class IOrderBoxServiceImpl implements IOrderBoxService { 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) { + public IOrderBoxServiceImpl(IYieldOrderService iYieldOrderService, ITaskService iTaskService, IOrderBindService iOrderBindService, IStationService iStationService, ILocationService iLocationService, AgvTaskTypeUtil agvTaskTypeUtil, IBsWorkCenterService bsWorkCenterService, IPipelineService iPipelineOrderBoxService) { this.iYieldOrderService = iYieldOrderService; this.iTaskService = iTaskService; this.iOrderBindService = iOrderBindService; this.iStationService = iStationService; this.iLocationService = iLocationService; + this.agvTaskTypeUtil = agvTaskTypeUtil; + this.bsWorkCenterService = bsWorkCenterService; + this.iPipelineOrderBoxService = iPipelineOrderBoxService; } + /** + * 构造器注入依赖服务(替代@Autowired,符合Spring最佳实践) + * + * @param iYieldOrderService 工单服务 + * @param iTaskService 任务服务 + * @param iOrderBindService 订单绑定服务 + * @param iStationService 站点服务 + * @param iLocationService 库位服务 + */ + + /** * 维护订单配件实际重量 * 功能:根据流程卡号更新对应工单的实际称重数据 * - * @param cardNo 流程卡号(唯一标识工单) + * @param cardNo 流程卡号(唯一标识工单) * @param actualWeight 实际称重值(单位:业务约定,如千克) * @return R 操作结果 - * - 成功:R.success() - * - 失败:R.fail(),携带具体失败原因 + * - 成功:R.success() + * - 失败:R.fail(),携带具体失败原因 */ @Override public R upholdOrderPartWeight(String cardNo, BigDecimal actualWeight) { - log.info("接收到实际重量:{},对应的流程卡号:{}",actualWeight,cardNo); + log.info("接收到实际重量:{},对应的流程卡号:{}", actualWeight, cardNo); // 根据流程卡号查询工单(按更新时间倒序,取最新记录) List list = iYieldOrderService.list(new QueryWrapper().eq("CARD_NO", cardNo).orderByDesc("UPDATE_TIME")); @@ -106,7 +125,7 @@ public class IOrderBoxServiceImpl implements IOrderBoxService { list.get(0).setActualWeighing(actualWeight); // 执行更新并返回结果 - return iYieldOrderService.updateById(list.get(0))? R.success():R.fail("实际称重维护:卡号维护失败"); + return iYieldOrderService.updateById(list.get(0)) ? R.success() : R.fail("实际称重维护:卡号维护失败"); } /** @@ -115,8 +134,8 @@ public class IOrderBoxServiceImpl implements IOrderBoxService { * * @param boxBinding 箱绑定参数(包含箱条码、订单ID列表、工位ID等) * @return R 绑定结果 - * - 成功:R.success() - * - 失败:R.fail(),携带具体失败原因(如参数为空、订单已绑定、重量超限等) + * - 成功:R.success() + * - 失败:R.fail(),携带具体失败原因(如参数为空、订单已绑定、重量超限等) */ @Override public R boxBinding(BoxBindingDto boxBinding) { @@ -154,7 +173,7 @@ public class IOrderBoxServiceImpl implements IOrderBoxService { } // 6. 完善任务信息并保存 - task= (Task) location.getData(); + task = (Task) location.getData(); task.setTaskStatus(Task.STATUS_START); // 设置任务初始状态为启动 task.setCreateTime(new Date()); // 设置任务创建时间 task.setCreateUser(AuthUtil.getUserId()); @@ -162,7 +181,7 @@ public class IOrderBoxServiceImpl implements IOrderBoxService { boolean orderBool = boxBinding.getOrderIdList() == null || boxBinding.getOrderIdList().size() == 0; if (orderBool) { task.setWeight(new BigDecimal(0)); - }else { + } else { task.setWeight(getWeightByOrderIdList(boxBinding.getOrderIdList())); } // 8. 重量校验(小于50kg才允许绑定,避免超重) @@ -178,75 +197,245 @@ public class IOrderBoxServiceImpl implements IOrderBoxService { // 10. 无订单则直接返回成功,有订单则执行订单绑定 if (orderBool) { return R.success(); - }else { - return saveOrderBindingList(task.getId(),boxBinding.getOrderIdList()); + } else { + return saveOrderBindingList(task.getId(), boxBinding.getOrderIdList()); } } + /** - * 箱条码与订单解绑 - * 流程:参数校验 → 查询运行中任务 → 重置站点/库位状态 → 解绑订单 → 结束任务 + * 释放站点并调用AGV小车接口 + *

+ * 业务场景:任务结束后释放指定站点,根据终点类型触发不同的AGV小车调度逻辑 + * 核心流程: + * 1. 校验入参和基础数据(站点、任务、终点信息) + * 2. 分两种场景处理AGV调度: + * - 场景1:仅指定终点名称(下料输送线)→ 直接调度AGV到下料线 + * - 场景2:指定终点站点ID → 调度AGV到目标空闲站点 + * 3. AGV调用失败时自动回滚站点状态,保证数据一致性 * - * @param boxBarcode 箱条码 - * @return R 解绑结果 - * - 成功:R.success() - * - 失败:R.fail(),携带具体失败原因(如箱条码为空) + * @param returnToWarehouseDto 回库请求参数DTO + * @return 统一返回结果 + * @throws IllegalArgumentException 入参无效、基础数据不存在时抛出 */ @Override - public R boxOrderUnbind(String boxBarcode) { - // 1. 参数非空校验 - if (boxBarcode.isEmpty()) { - log.warn("箱绑定参数为空或箱条码缺失"); - return R.fail("箱条码不能为空"); + public R inventoryReturnToWarehouse(ReturnToWarehouseDto returnToWarehouseDto) { + // ========== 1. 入参和基础数据校验(前置校验,提前失败) ========== + // 校验DTO非空 + Assert.notNull(returnToWarehouseDto, "回库请求参数不能为空"); + String stationCode = returnToWarehouseDto.getStationCode(); + Assert.hasText(stationCode, "站点编号不能为空"); + + // 查询站点信息(封装为方法,提升可读性) + Station targetStation = getStationByCode(stationCode); + // 查询有效任务列表 + List validTaskList = getValidTaskList(stationCode); + // 校验站点状态 + checkStationStatus(targetStation); + // 校验终点信息 + checkEndLocationInfo(returnToWarehouseDto); + + // ========== 2. 分场景处理AGV调度 ========== + try { + if (isDropoffConveyorLineScenario(returnToWarehouseDto)) { + // 场景1:仅指定下料输送线名称 → 调度AGV到下料线 + return handleDropoffConveyorLineScenario(targetStation, validTaskList); + } else { + // 场景2:指定终点站点ID → 调度AGV到目标站点 + return handleTargetStationScenario(targetStation, returnToWarehouseDto, validTaskList); + } + } catch (Exception e) { + log.error("库存回库-AGV调度异常,站点编号:{}", stationCode, e); + return R.fail("库存回库操作失败:" + e.getMessage()); } + } - // 2. 查询该箱条码对应的运行中任务 - List taskList = iTaskService.list( - new LambdaQueryWrapper().eq(Task::getBoxBarcode, boxBarcode).in(false,Task::getTaskStatus,RUNNING_STATUSES) - ); + /** + * 根据站点编号查询站点信息(不存在则抛异常) + */ + private Station getStationByCode(String stationCode) { + LambdaQueryWrapper stationQuery = new LambdaQueryWrapper() + .eq(Station::getStationCode, stationCode); + List stationList = iStationService.list(stationQuery); + + if (stationList == null || stationList.isEmpty()) { + log.warn("站点编号不存在,入参:{}", stationCode); + throw new IllegalArgumentException("输入站点编号不存在"); + } + return stationList.get(0); + } - // 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); - } + /** + * 查询有效任务列表(运行中状态,不存在则抛异常) + */ + private List getValidTaskList(String stationCode) { + LambdaQueryWrapper taskQuery = new LambdaQueryWrapper() + .eq(Task::getBoxBarcode, stationCode) + .in(Task::getTaskStatus, RUNNING_STATUSES); // RUNNING_STATUSES建议抽取为常量 + List taskList = iTaskService.list(taskQuery); + + if (taskList == null || taskList.isEmpty()) { + log.warn("箱条码数据异常,站点编号:{}", stationCode); + throw new IllegalArgumentException("箱条码数据异常"); + } + return taskList; + } - // 3.2 重置库位状态为占用(释放库位) - if (task.getLocationId()!=null&&task.getLocationId()!=0) { - Location location = iLocationService.getById(task.getLocationId()); - location.setLocationStatus(STATUS_OCCUPIED); - iLocationService.updateById(location); + /** + * 校验站点状态(必须为空闲) + */ + private void checkStationStatus(Station station) { + if (!STATUS_FREE.equals(station.getStationStatus())) { + log.warn("站点被占用,站点编号:{},当前状态:{}", station.getStationCode(), station.getStationStatus()); + throw new IllegalArgumentException("该站点正在被使用,请使用其他站点"); + } + } + + /** + * 校验终点信息(终点ID/名称不能为空) + */ + private void checkEndLocationInfo(ReturnToWarehouseDto dto) { + Long endLocationId = dto.getEndLocationId(); + String endName = dto.getEndName(); + + // 终点ID和名称都为空 → 异常 + if ((endLocationId == null || endLocationId == 0) && (endName == null || endName.isBlank())) { + throw new IllegalArgumentException("请检查运送线终点,运送线终点为空"); + } + + // 仅终点ID为空 → 校验终点名称是否为下料线 + if ((endLocationId == null || endLocationId == 0) && !DROPOFF_CONVEYOR_LINE_NAME.equals(endName)) { + throw new IllegalArgumentException("请检查运送线终点位置异常"); + } + } + + /** + * 判断是否为「下料输送线」场景 + */ + private boolean isDropoffConveyorLineScenario(ReturnToWarehouseDto dto) { + Long endLocationId = dto.getEndLocationId(); + return endLocationId == null || endLocationId == 0; + } + + /** + * 处理「下料输送线」场景的AGV调度 + */ + private R handleDropoffConveyorLineScenario(Station targetStation, List taskList) { + String stationCode = targetStation.getStationCode(); + // 1. 占用站点 + updateStationStatus(targetStation, STATUS_OCCUPIED); + + try { + // 2. 获取AGV任务类型并调用调度接口 + String taskType = agvTaskTypeUtil.getTaskType(Integer.valueOf(targetStation.getStationPosition()), true); + boolean agvResult = iPipelineOrderBoxService.genAgvSchedulingTask( + taskType, stationCode, DROPOFF_CONVEYOR_LINE, EQUIPMENT_TYPE_AGV, taskList.get(0) + ); + + if (agvResult) { + log.info("AGV调度成功(下料线场景),站点编号:{}", stationCode); + return R.success(); + } else { + throw new RuntimeException("调用AGV小车异常"); } + } catch (Exception e) { + // 3. AGV调用失败 → 回滚站点状态 + updateStationStatus(targetStation, STATUS_FREE); + throw e; + } + } - // 3.3 解绑订单(更新绑定状态为未绑定) - List orderBindList = iOrderBindService.list(new LambdaQueryWrapper().eq(OrderBind::getTaskId, task.getId())); - if (!CollectionUtils.isEmpty(orderBindList)) { - for (OrderBind orderBind : orderBindList) { - orderBind.setBindingStatus(STATUS_UNBINDED); - iOrderBindService.updateById(orderBind); - } + /** + * 处理「目标站点」场景的AGV调度 + */ + private R handleTargetStationScenario(Station targetStation, ReturnToWarehouseDto dto, List taskList) { + String sourceStationCode = targetStation.getStationCode(); + Long endLocationId = dto.getEndLocationId(); + + // 1. 查询终点空闲站点 + Station endStation = getFreeStationByWcId(endLocationId); + + // 2. 占用源站点 + 预占用终点站点 + updateStationStatus(targetStation, STATUS_OCCUPIED); + updateStationStatus(endStation, PRE_STATUS_OCCUPIED); + + try { + // 3. 获取AGV任务类型并调用调度接口 + String taskType = agvTaskTypeUtil.getTaskType(Integer.valueOf(targetStation.getStationPosition()), false); + boolean agvResult = iPipelineOrderBoxService.genAgvSchedulingTask( + taskType, sourceStationCode, endStation.getStationCode(), EQUIPMENT_TYPE_AGV, taskList.get(0) + ); + + if (agvResult) { + log.info("AGV调度成功(目标站点场景),源站点:{},终点站点:{}", sourceStationCode, endStation.getStationCode()); + return R.success(); + } else { + throw new RuntimeException("调用AGV小车异常"); } + } catch (Exception e) { + // 4. AGV调用失败 → 回滚两个站点状态 + updateStationStatus(targetStation, STATUS_FREE); + updateStationStatus(endStation, STATUS_FREE); + throw e; + } + } -// // 3.4 结束任务(更新任务状态为已完成) -// task.setTaskStatus(STATUS_FINISHED); -// iTaskService.updateById(task); + /** + * 根据WCID查询空闲的终点站点(不存在则抛异常) + */ + private Station getFreeStationByWcId(Long wcId) { + LambdaQueryWrapper endStationQuery = new LambdaQueryWrapper() + .eq(Station::getWcId, wcId) + .eq(Station::getStationStatus, STATUS_FREE); + List endStationList = iStationService.list(endStationQuery); + + if (endStationList == null || endStationList.isEmpty()) { + log.warn("结束站点异常,WCID:{}", wcId); + throw new IllegalArgumentException("结束站点异常"); } + return endStationList.get(0); + } - return R.success(); + /** + * 统一更新站点状态(封装重复逻辑) + */ + private void updateStationStatus(Station station, Integer newStatus) { + station.setStationStatus(newStatus); + boolean updateResult = iStationService.updateById(station); + if (!updateResult) { + log.error("站点状态更新失败,站点编号:{},目标状态:{}", station.getStationCode(), newStatus); + throw new RuntimeException("站点状态更新失败"); + } + log.debug("站点状态更新成功,站点编号:{},状态:{}", station.getStationCode(), newStatus); + } + + @Override + public R returnToWarehouseList() { + List stationList = iStationService.list(new LambdaQueryWrapper().eq(Station::getStationStatus, STATUS_FREE)); + ArrayList bsWorkCenterVOList = new ArrayList<>(); + BsWorkCenterVO bsWorkCenterVO = new BsWorkCenterVO(); + bsWorkCenterVO.setWcName(DROPOFF_CONVEYOR_LINE_NAME); + bsWorkCenterVOList.add(bsWorkCenterVO); + if (!stationList.isEmpty()) { + List list = stationList.stream().map(Station::getWcId).distinct().collect(Collectors.toList()); + List bwList = bsWorkCenterService.getByIds(list); + if (!bwList.isEmpty()) { + bwList.forEach(s->{bsWorkCenterVOList.add(s);}); + } + + } + return R.data(bsWorkCenterVOList); } /** * 批量保存订单与任务的绑定关系 * 核心:创建订单绑定记录并批量插入,插入失败则回滚任务(删除已创建的任务) * - * @param taskId 任务ID(关联箱条码) + * @param taskId 任务ID(关联箱条码) * @param orderIdList 订单ID列表 * @return R 保存结果 - * - 成功:R.success() - * - 失败:R.fail(),删除任务并返回异常信息 + * - 成功:R.success() + * - 失败:R.fail(),删除任务并返回异常信息 */ private R saveOrderBindingList(Long taskId, ArrayList orderIdList) { ArrayList orderBindList = new ArrayList<>(); @@ -262,7 +451,7 @@ public class IOrderBoxServiceImpl implements IOrderBoxService { // 批量插入绑定记录,失败则删除任务 if (iOrderBindService.saveBatch(orderBindList)) { return R.success(); - }else { + } else { iTaskService.removeById(taskId); // 回滚:删除已创建的任务 return R.fail("订单绑定箱条码异常"); } @@ -292,8 +481,8 @@ public class IOrderBoxServiceImpl implements IOrderBoxService { * * @param task 任务对象(包含工位ID) * @return R 分配结果 - * - 成功:R.data(task),任务对象已填充站点/库位ID - * - 失败:R.fail(),提示库位繁忙 + * - 成功:R.data(task),任务对象已填充站点/库位ID + * - 失败:R.fail(),提示库位繁忙 */ private R getSiteLocation(Task task) { // 1. 查询当前工位可用的站点(状态为占用的站点) @@ -328,8 +517,8 @@ public class IOrderBoxServiceImpl implements IOrderBoxService { * * @param boxBarcode 箱条码 * @return R 校验结果 - * - 成功:R.success()(无运行中任务) - * - 失败:R.fail()(存在运行中任务) + * - 成功:R.success()(无运行中任务) + * - 失败:R.fail()(存在运行中任务) */ private R checkBoxBarcodeRunningTask(String boxBarcode) { // 查询箱条码对应的所有任务 @@ -361,8 +550,8 @@ public class IOrderBoxServiceImpl implements IOrderBoxService { * * @param orderIdList 订单ID列表 * @return R 校验结果 - * - 成功:R.success()(无已绑定订单) - * - 失败:R.fail()(存在已绑定订单,返回对应流程卡号) + * - 成功:R.success()(无已绑定订单) + * - 失败:R.fail()(存在已绑定订单,返回对应流程卡号) */ private R checkOrderIdBoundStatus(List orderIdList) { // 无订单号,直接通过校验 diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/IPipelineOrderBoxServiceImpl.java b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/IPipelineOrderBoxServiceImpl.java new file mode 100644 index 00000000..925c8d59 --- /dev/null +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/IPipelineOrderBoxServiceImpl.java @@ -0,0 +1,123 @@ +package org.springblade.desk.logistics.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.extern.slf4j.Slf4j; +import org.springblade.core.tool.api.R; +import org.springblade.desk.logistics.pojo.entity.Location; +import org.springblade.desk.logistics.pojo.entity.OrderBind; +import org.springblade.desk.logistics.pojo.entity.Station; +import org.springblade.desk.logistics.pojo.entity.Task; +import org.springblade.desk.logistics.service.*; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static org.springblade.desk.logistics.pojo.entity.OrderBind.STATUS_UNBINDED; +import static org.springblade.desk.logistics.pojo.entity.Station.STATUS_OCCUPIED; + +@Service +@Slf4j +public class IPipelineOrderBoxServiceImpl implements IPipelineOrderBoxService { + /** + * 任务服务:处理箱绑定任务的创建、状态更新、删除等 + */ + private final ITaskService iTaskService; + /** + * 站点服务:处理站点状态(占用/空闲)管理 + */ + private final IStationService iStationService; + + /** + * 库位服务:处理库位状态(占用/空闲)管理 + */ + private final ILocationService iLocationService; + /** + * 订单绑定服务:处理订单与任务的绑定关系维护 + */ + private final IOrderBindService iOrderBindService; + + /** + * 任务运行中状态集合:包含任务从启动到待入库的所有中间状态 + * 用于判断箱条码是否存在未完成的运行任务 + */ + private static final Set 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);// 返库状态 + } + + public IPipelineOrderBoxServiceImpl(ITaskService iTaskService, IStationService iStationService, ILocationService iLocationService, IOrderBindService iOrderBindService) { + this.iTaskService = iTaskService; + this.iStationService = iStationService; + this.iLocationService = iLocationService; + this.iOrderBindService = iOrderBindService; + } + + + /** + * 箱条码与订单解绑 + * 流程:参数校验 → 查询运行中任务 → 重置站点/库位状态 → 解绑订单 → 结束任务 + * + * @param boxBarcode 箱条码 + * @return R 解绑结果 + * - 成功:R.success() + * - 失败:R.fail(),携带具体失败原因(如箱条码为空) + */ + @Override + public R boxOrderUnbind(String boxBarcode) { + // 1. 参数非空校验 + if (boxBarcode.isEmpty()) { + log.warn("箱绑定参数为空或箱条码缺失"); + return R.fail("箱条码不能为空"); + } + + // 2. 查询该箱条码对应的运行中任务 + List taskList = iTaskService.list( + new LambdaQueryWrapper().eq(Task::getBoxBarcode, boxBarcode).in(false, 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 orderBindList = iOrderBindService.list(new LambdaQueryWrapper().eq(OrderBind::getTaskId, task.getId())); + if (!CollectionUtils.isEmpty(orderBindList)) { + for (OrderBind orderBind : orderBindList) { + orderBind.setBindingStatus(STATUS_UNBINDED); + iOrderBindService.updateById(orderBind); + } + } + +// // 3.4 结束任务(更新任务状态为已完成) +// task.setTaskStatus(STATUS_FINISHED); +// iTaskService.updateById(task); + } + + return R.success(); + } + +} diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/PipelineServiceImpl.java b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/PipelineServiceImpl.java index 268179f8..b17bb2cb 100644 --- a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/PipelineServiceImpl.java +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/service/impl/PipelineServiceImpl.java @@ -24,6 +24,7 @@ * Author: Chill Zhuang (bladejava@qq.com) */ package org.springblade.desk.logistics.service.impl; +import groovy.lang.Lazy; import lombok.extern.slf4j.Slf4j; import org.springblade.core.tool.api.R; import org.springblade.desk.logistics.constant.AgvConstant; @@ -60,7 +61,8 @@ public class PipelineServiceImpl implements IPipelineService { ILocationService locationService; @Autowired - IOrderBoxService orderBoxService; + IPipelineOrderBoxService iPipelineOrderBoxService; +// IOrderBoxService orderBoxService; @Autowired ITaskExecuteRecordService taskExecuteRecordService; @@ -80,7 +82,7 @@ public class PipelineServiceImpl implements IPipelineService { taskService.savePipelineWeigh(boxBarcode,actualWeight, Task.STATUS_RETURNED); // 3.超重处理,解绑 - R ret = orderBoxService.boxOrderUnbind(boxBarcode); + R ret = iPipelineOrderBoxService.boxOrderUnbind(boxBarcode); if(!ret.isSuccess()){ throw new ServiceException("解绑失败: "+boxBarcode); } diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/utils/AgvTaskTypeUtil.java b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/utils/AgvTaskTypeUtil.java index 4a9da47f..6ca253d3 100644 --- a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/utils/AgvTaskTypeUtil.java +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/utils/AgvTaskTypeUtil.java @@ -15,7 +15,7 @@ public class AgvTaskTypeUtil { * @param isRecycle 是否回收 (true:空箱回收, false:正常任务) * @return 任务类型 (QM3, QM5, QM6, QM7) */ - public String getTaskType(Integer floor, Boolean isRecycle) { + public String getTaskType(Integer floor, Boolean isRecycle) { // 参数校验 if (floor == null) { log.error("楼层参数不能为空"); diff --git a/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/utils/CollectionCheckUtil.java b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/utils/CollectionCheckUtil.java new file mode 100644 index 00000000..94a32736 --- /dev/null +++ b/blade-service/blade-desk/src/main/java/org/springblade/desk/logistics/utils/CollectionCheckUtil.java @@ -0,0 +1,36 @@ +package org.springblade.desk.logistics.utils; + +import java.util.Collection; +import java.util.Map; + +public class CollectionCheckUtil { + /** + * 判断指定字段的值是否存在于目标集合中 + * + * @param fieldValue 要校验的字段参数值 + * @param targetCollection 目标集合(List、Set、Map等) + * @param 字段值的类型(泛型适配任意类型) + * @return 存在返回true,不存在返回false + * @throws IllegalArgumentException 当目标集合为空或类型不支持时抛出异常 + */ + public static boolean isFieldInCollection(T fieldValue, Object targetCollection) { + // 健壮性校验 + if (targetCollection == null) { + throw new IllegalArgumentException("目标集合不能为空"); + } + + // 处理Collection类型(List、Set等) + if (targetCollection instanceof Collection) { + return ((Collection) targetCollection).contains(fieldValue); + } + + // 处理Map类型(判断key是否存在) + if (targetCollection instanceof Map) { + return ((Map) targetCollection).containsKey(fieldValue); + } + + // 不支持的集合类型 + throw new IllegalArgumentException("不支持的集合类型:" + targetCollection.getClass().getName()); + } + +}