diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderService.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderService.java index e30de72..9031e26 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderService.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderService.java @@ -1,20 +1,20 @@ package com.viewsh.module.ops.environment.service.cleanorder; - import com.viewsh.module.ops.environment.dal.dataobject.CleanOrderAutoCreateReqDTO; -import com.viewsh.module.ops.environment.dal.dataobject.CleanOrderPauseReqDTO; -import com.viewsh.module.ops.environment.dal.dataobject.CleanOrderResumeReqDTO; /** * 保洁工单服务 - * + *

* 职责: * 1. 自动创建保洁工单 * 2. 队列推送管理(空闲推送,忙碌入队) * 3. 保洁特有的状态转换(confirmOrder、信标触发) - * 4. 作业时长计算 - * 5. 保洁扩展信息管理 - * 6. 并发控制和去重合并播报 + * 4. 任务自动切换 + * 5. 语音播报管理 + *

+ * 变更说明: + * - 移除了暂停/恢复/打断方法(由 {@link com.viewsh.module.ops.core.lifecycle.OrderLifecycleManager} 处理) + * - 移除了作业时长计算方法(由 {@link com.viewsh.module.ops.environment.handler.CleanOrderEventHandler} 通过事件处理) * * @author lzh */ @@ -72,8 +72,6 @@ public interface CleanOrderService { * 感知信标,开始作业 * 状态转换:CONFIRMED → ARRIVED * - * 注意:此方法由IoT信标事件触发,暂不实现,留接口 - * * @param orderId 工单ID * @param cleanerId 保洁员ID * @param beaconId 信标ID @@ -85,8 +83,6 @@ public interface CleanOrderService { * 状态转换:ARRIVED → COMPLETED * 完成后自动推送队列中的下一个任务 * - * 注意:此方法由IoT信标事件触发,暂不实现,留接口 - * * @param orderId 工单ID * @param cleanerId 保洁员ID * @param beaconId 信标ID @@ -132,17 +128,8 @@ public interface CleanOrderService { */ void playVoiceForNextTask(Long cleanerId, int queueCount, String nextTaskTitle); - // ========== 暂停/恢复(调用通用服务)========== - - /** - * 暂停保洁工单(状态转换:ARRIVED -> PAUSED) - * 场景:P0紧急工单插队、临时离开 - * - * @param pauseReq 暂停请求 - */ - void pauseCleanOrder(CleanOrderPauseReqDTO pauseReq); - // ========== 优先级管理 ========== + /** * 升级工单优先级为P0并处理紧急插队 * 场景:巡检员手动将普通工单升级为P0紧急任务 @@ -153,13 +140,6 @@ public interface CleanOrderService { */ boolean upgradePriorityToP0(Long orderId, String reason); - /** - * 恢复保洁工单(状态转换:PAUSED -> ARRIVED) - * - * @param resumeReq 恢复请求 - */ - void resumeCleanOrder(CleanOrderResumeReqDTO resumeReq); - // ========== 作业时长计算 ========== /** @@ -169,21 +149,4 @@ public interface CleanOrderService { * @return 作业时长(秒) */ Integer calculateActualDuration(Long orderId); - - /** - * 记录到岗时间 - * 场景:状态机监听器调用 - * - * @param orderId 工单ID - */ - void recordArrivedTime(Long orderId); - - /** - * 计算并更新作业时长 - * 场景:工单完成时,状态机监听器调用 - * - * @param orderId 工单ID - */ - void calculateDuration(Long orderId); - } diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java index 2f01ab9..7a2d9c8 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanOrderServiceImpl.java @@ -1,24 +1,23 @@ package com.viewsh.module.ops.environment.service.cleanorder; -import com.viewsh.module.ops.api.dispatch.DispatchEngineService; import com.viewsh.module.ops.api.queue.OrderQueueDTO; import com.viewsh.module.ops.api.queue.OrderQueueService; -import com.viewsh.module.ops.dal.dataobject.cleaner.OpsCleanerStatusDO; -import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderCleanExtDO; +import com.viewsh.module.ops.core.dispatch.DispatchEngine; +import com.viewsh.module.ops.core.dispatch.model.DispatchResult; +import com.viewsh.module.ops.core.dispatch.model.OrderDispatchContext; +import com.viewsh.module.ops.core.lifecycle.OrderLifecycleManager; +import com.viewsh.module.ops.core.lifecycle.model.OrderTransitionRequest; import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; -import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderCleanExtMapper; import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; -import com.viewsh.module.ops.enums.CleanerStatusEnum; import com.viewsh.module.ops.enums.OperatorTypeEnum; import com.viewsh.module.ops.enums.PriorityEnum; import com.viewsh.module.ops.enums.WorkOrderStatusEnum; import com.viewsh.module.ops.environment.dal.dataobject.CleanOrderAutoCreateReqDTO; -import com.viewsh.module.ops.environment.dal.dataobject.CleanOrderPauseReqDTO; -import com.viewsh.module.ops.environment.dal.dataobject.CleanOrderResumeReqDTO; +import com.viewsh.module.ops.environment.dal.dataobject.workorder.OpsOrderCleanExtDO; +import com.viewsh.module.ops.environment.dal.mysql.workorder.OpsOrderCleanExtMapper; +import com.viewsh.module.ops.environment.handler.CleanOrderEventHandler; import com.viewsh.module.ops.environment.service.cleaner.CleanerStatusService; -import com.viewsh.module.ops.environment.service.dispatch.CleanerAreaPriorityStrategy; -import com.viewsh.module.ops.environment.service.voice.VoiceBroadcastDeduplicationService; -import com.viewsh.module.ops.service.fsm.OrderStateMachine; +import com.viewsh.module.ops.environment.service.dispatch.CleanerAreaAssignStrategy; import com.viewsh.module.ops.service.order.OpsOrderService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; @@ -27,10 +26,25 @@ import org.springframework.transaction.annotation.Transactional; import java.time.Duration; import java.time.LocalDateTime; -import java.util.List; /** - * 保洁工单扩展服务实现 + * 保洁工单服务实现(简化版) + *

+ * 职责: + * 1. 工单创建与自动分配 + * 2. 保洁特有的状态转换编排(confirm, arrive, complete) + * 3. 作业时长计算 + *

+ * 架构说明: + * - 状态同步:委托给 {@link OrderLifecycleManager} + * - 派单决策:使用新的 {@link DispatchEngine} + * - 保洁员状态:由 {@link com.viewsh.module.ops.environment.integration.listener.CleanerStateChangeListener} 处理 + * - 通知逻辑:由 {@link CleanOrderEventHandler} 处理 + *

+ * 变更历史: + * - Phase 3: 使用新的调度引擎和生命周期管理器 + * - Phase 3: 移除直接的状态管理代码(改为事件驱动) + * - Phase 3: 瘦身代码,专注保洁业务编排 * * @author lzh */ @@ -51,19 +65,21 @@ public class CleanOrderServiceImpl implements CleanOrderService { private OrderQueueService orderQueueService; @Resource - private CleanerAreaPriorityStrategy cleanerAreaPriorityStrategy; + private CleanerAreaAssignStrategy cleanerAreaAssignStrategy; @Resource private CleanerStatusService cleanerStatusService; @Resource - private VoiceBroadcastDeduplicationService voiceBroadcastDeduplicationService; + private DispatchEngine dispatchEngine; @Resource - private OrderStateMachine orderStateMachine; + private OrderLifecycleManager orderLifecycleManager; @Resource - private DispatchEngineService dispatchEngineService; + private CleanOrderEventHandler cleanOrderEventHandler; + + // ==================== 工单创建 ==================== @Override @Transactional(rollbackFor = Exception.class) @@ -74,122 +90,75 @@ public class CleanOrderServiceImpl implements CleanOrderService { // 2. 创建保洁扩展信息 OpsOrderCleanExtDO cleanExt = OpsOrderCleanExtDO.builder() .opsOrderId(orderId) - .isAuto(1) // 自动工单 + .isAuto(1) .expectedDuration(createReq.getExpectedDuration()) .cleaningType(createReq.getCleaningType()) .difficultyLevel(createReq.getDifficultyLevel()) .build(); - cleanExtMapper.insert(cleanExt); log.info("创建自动保洁工单成功: orderId={}, expectedDuration={}分钟", orderId, createReq.getExpectedDuration()); - // 3. 自动分配保洁员并入队 - enqueueOrder(orderId, createReq.getAreaId(), PriorityEnum.fromPriority(createReq.getPriority())); + // 3. 调度引擎分配 + autoAssignOrder(orderId, createReq.getAreaId(), PriorityEnum.fromPriority(createReq.getPriority())); return orderId; } /** - * 工单入队(根据保洁员状态选择不同策略) - * 推荐保洁员、入队、派单、同步状态 - * - * @param orderId 工单ID - * @param areaId 区域ID - * @param priority 优先级 + * 自动分配工单(使用新的调度引擎) */ - private void enqueueOrder(Long orderId, Long areaId, PriorityEnum priority) { + private void autoAssignOrder(Long orderId, Long areaId, PriorityEnum priority) { try { - // 1. 推荐保洁员 - Long recommendedCleanerId = cleanerAreaPriorityStrategy.recommendCleanerForNewOrder(areaId, priority); - - if (recommendedCleanerId == null) { - log.warn("未找到可用的保洁员,工单将无法自动派单: orderId={}, areaId={}", orderId, areaId); - // 工单保持在PENDING状态,等待后续处理 + // 查询工单信息 + OpsOrderDO order = opsOrderMapper.selectById(orderId); + if (order == null) { + log.warn("工单不存在,无法自动分配: orderId={}", orderId); return; } - // 2. 查询保洁员状态 - OpsCleanerStatusDO cleanerStatusDO = - cleanerStatusService.getStatus(recommendedCleanerId); + // 构建调度上下文 + OrderDispatchContext context = OrderDispatchContext.builder() + .orderId(order.getId()) + .orderCode(order.getOrderCode()) + .orderTitle(order.getTitle()) + .businessType(order.getOrderType()) + .areaId(areaId) + .priority(priority) + .build(); - if (cleanerStatusDO == null) { - log.warn("保洁员状态不存在,无法派单: cleanerId={}", recommendedCleanerId); - return; - } + // 使用调度引擎执行调度 + DispatchResult result = dispatchEngine.dispatch(context); - CleanerStatusEnum cleanerStatus = cleanerStatusDO.getStatusEnum(); - - // 3. P0 紧急任务特殊处理 - boolean isUrgent = priority == PriorityEnum.P0; - - if (cleanerStatus == CleanerStatusEnum.IDLE) { - // 空闲状态:立即推送 - log.info("保洁员空闲,立即推送工单: cleanerId={}, isUrgent={}", recommendedCleanerId, isUrgent); - enqueueAndDispatch(orderId, recommendedCleanerId, isUrgent); - } else if (cleanerStatus == CleanerStatusEnum.BUSY) { - if (isUrgent) { - // P0 紧急任务:使用派单引擎处理(可能打断当前任务) - log.warn("保洁员忙碌但为P0紧急任务,使用紧急派单: cleanerId={}", recommendedCleanerId); - enqueueAndDispatch(orderId, recommendedCleanerId, true); - } else { - // 忙碌状态:只入队 - log.info("保洁员忙碌,工单入队等待: cleanerId={}", recommendedCleanerId); - enqueueOrderOnly(orderId, recommendedCleanerId); - - // 语音提示待办数量 - int queueCount = getQueueCount(recommendedCleanerId); - playVoiceForQueuedOrder(recommendedCleanerId, queueCount); - } + if (result.isSuccess()) { + log.info("自动分配成功: orderId={}, assigneeId={}, path={}", + orderId, result.getAssigneeId(), result.getPath()); } else { - log.warn("保洁员状态不可接单: cleanerId={}, status={}", - recommendedCleanerId, cleanerStatus); + log.warn("自动分配失败: orderId={}, reason={}", orderId, result.getMessage()); } } catch (Exception e) { - log.error("工单入队或派单失败: orderId={}, areaId={}", orderId, areaId, e); - // TODO: 可以考虑回滚工单创建,或者标记为"入队失败"状态 + log.error("自动分配工单失败: orderId={}", orderId, e); } } - // ========== 队列推送机制实现 ========== + // ==================== 队列推送(兼容旧接口)==================== @Override @Transactional(rollbackFor = Exception.class) public void enqueueOrderOnly(Long orderId, Long cleanerId) { - log.info("工单入队(不推送): orderId={}, cleanerId={}", orderId, cleanerId); + // 使用生命周期管理器入队 + OrderTransitionRequest request = OrderTransitionRequest.builder() + .orderId(orderId) + .assigneeId(cleanerId) + .operatorType(OperatorTypeEnum.SYSTEM) + .operatorId(cleanerId) + .reason("执行人忙碌,任务入队") + .build(); - // 1. 查询工单 - OpsOrderDO order = opsOrderMapper.selectById(orderId); - if (order == null) { - log.error("工单不存在: orderId={}", orderId); - throw new IllegalArgumentException("工单不存在: " + orderId); - } - - // 2. 设置执行人 - order.setAssigneeId(cleanerId); - opsOrderMapper.updateById(order); - - // 3. 使用状态机转换状态:PENDING → QUEUED - orderStateMachine.transition( - order, - WorkOrderStatusEnum.QUEUED, - OperatorTypeEnum.SYSTEM, - cleanerId, - "工单入队,等待推送" - ); - - // 4. 创建队列记录(使用 OrderQueueService,等待后续推送) - Long queueId = orderQueueService.enqueue( - orderId, - cleanerId, - PriorityEnum.fromPriority(order.getPriority()), - null // queueIndex 自动计算 - ); - - log.info("工单已入队等待: orderId={}, cleanerId={}, queueId={}, status=QUEUED", - orderId, cleanerId, queueId); + orderLifecycleManager.enqueue(request); + log.info("工单已入队: orderId={}, cleanerId={}", orderId, cleanerId); } @Override @@ -198,183 +167,64 @@ public class CleanOrderServiceImpl implements CleanOrderService { enqueueAndDispatch(orderId, cleanerId, false); } - /** - * 工单入队并立即推送(内部方法,支持紧急派单) - * - * @param orderId 工单ID - * @param cleanerId 保洁员ID - * @param isUrgent 是否为紧急任务(P0) - */ @Transactional(rollbackFor = Exception.class) public void enqueueAndDispatch(Long orderId, Long cleanerId, boolean isUrgent) { - log.info("工单入队并立即推送: orderId={}, cleanerId={}, isUrgent={}", orderId, cleanerId, isUrgent); - - // 1. 查询工单 + // 查询工单 OpsOrderDO order = opsOrderMapper.selectById(orderId); if (order == null) { log.error("工单不存在: orderId={}", orderId); - throw new IllegalArgumentException("工单不存在: " + orderId); + return; } - // 2. 更新工单状态:PENDING → QUEUED - order.setStatus(WorkOrderStatusEnum.QUEUED.getStatus()); - order.setAssigneeId(cleanerId); - opsOrderMapper.updateById(order); - - // 3. 创建队列记录 - Long queueId = orderQueueService.enqueue( - orderId, - cleanerId, - PriorityEnum.fromPriority(order.getPriority()), - null - ); - - // 4. 立即推送到工牌(根据是否紧急使用不同的派单策略) if (isUrgent) { - // P0 紧急任务:使用派单引擎的紧急派单(可能打断当前任务) - boolean success = dispatchEngineService.urgentDispatch(queueId); - if (!success) { - log.warn("紧急派单失败,回退到普通派单: queueId={}", queueId); - dispatchToCleaner(queueId); - } + // P0紧急任务:使用紧急插队 + DispatchResult result = dispatchEngine.urgentInterrupt(orderId, cleanerId); + log.warn("P0紧急派单结果: orderId={}, success={}", orderId, result.isSuccess()); } else { // 普通任务:正常派单 - dispatchToCleaner(queueId); + OrderTransitionRequest request = OrderTransitionRequest.builder() + .orderId(orderId) + .targetStatus(WorkOrderStatusEnum.DISPATCHED) + .assigneeId(cleanerId) + .operatorType(OperatorTypeEnum.SYSTEM) + .operatorId(cleanerId) + .reason("自动派单") + .build(); + + orderLifecycleManager.dispatch(request); + log.info("工单已派发: orderId={}, cleanerId={}", orderId, cleanerId); } - // 5. 语音播报 - playVoiceForNewOrder(cleanerId); + // 语音播报 + cleanOrderEventHandler.sendNewOrderNotification(cleanerId, orderId); } @Override @Transactional(rollbackFor = Exception.class) public void dispatchToCleaner(Long queueId) { - log.info("推送工单到工牌: queueId={}", queueId); - - // 1. 查询队列记录 + // 查询队列记录 OrderQueueDTO queueDTO = orderQueueService.getById(queueId); if (queueDTO == null) { log.error("队列记录不存在: queueId={}", queueId); return; } - // 2. 并发控制:检查是否已有任务在推送中(状态为DISPATCHED) - boolean hasDispatchingTask = checkIfHasDispatchingTask(queueDTO.getUserId()); - if (hasDispatchingTask) { - log.warn("保洁员已有任务在推送中,跳过本次推送: queueId={}, cleanerId={}", - queueId, queueDTO.getUserId()); - return; - } + // 使用生命周期管理器派单 + OrderTransitionRequest request = OrderTransitionRequest.builder() + .orderId(queueDTO.getOpsOrderId()) + .targetStatus(WorkOrderStatusEnum.DISPATCHED) + .assigneeId(queueDTO.getUserId()) + .queueId(queueId) + .operatorType(OperatorTypeEnum.SYSTEM) + .operatorId(queueDTO.getUserId()) + .reason("推送工单") + .build(); - // 3. 查询工单 - OpsOrderDO order = opsOrderMapper.selectById(queueDTO.getOpsOrderId()); - if (order == null) { - log.error("工单不存在: orderId={}", queueDTO.getOpsOrderId()); - return; - } - - // 4. 使用派单引擎进行自动派单(管理队列状态转换) - boolean dispatchSuccess = dispatchEngineService.autoDispatch(queueId); - - if (!dispatchSuccess) { - log.error("派单失败: queueId={}", queueId); - return; - } - - // 5. 使用状态机转换状态:QUEUED → DISPATCHED - orderStateMachine.transition( - order, - WorkOrderStatusEnum.DISPATCHED, - OperatorTypeEnum.SYSTEM, - queueDTO.getUserId(), - "推送工单" - ); - - log.info("工单已推送到工牌: queueId={}, orderId={}, cleanerId={}, status=DISPATCHED", - queueId, queueDTO.getOpsOrderId(), queueDTO.getUserId()); - - // TODO: 调用 IoT 服务推送工牌通知 - // iotDeviceService.pushNotification(queueDTO.getUserId(), - // "您有新的工单:" + order.getTitle()); - // iotDeviceService.vibrate(queueDTO.getUserId(), 1000); + orderLifecycleManager.dispatch(request); + log.info("工单已推送: queueId={}, orderId={}", queueId, queueDTO.getOpsOrderId()); } - /** - * 检查保洁员是否已有任务在推送中(状态为DISPATCHED) - * 用于并发控制,防止重复推送 - */ - private boolean checkIfHasDispatchingTask(Long cleanerId) { - // 查询该保洁员是否有状态为 DISPATCHED 的队列记录 - List tasks = - orderQueueService.getTasksByUserId(cleanerId); - - return tasks.stream() - .anyMatch(task -> "DISPATCHED".equals(task.getQueueStatus())); - } - - /** - * 查询保洁员在队列中的待办工单数量 - */ - private int getQueueCount(Long cleanerId) { - Long count = orderQueueService.countByUserId(cleanerId); - return count != null ? count.intValue() : 0; - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void pauseCleanOrder(CleanOrderPauseReqDTO pauseReq) { - // 1. 调用基础服务暂停工单 - opsOrderService.pauseOrder(pauseReq.getOrderId(), pauseReq.getCleanerId(), pauseReq.getPauseReason()); - - // 2. 同步更新队列状态:DISPATCHED → PAUSED - OrderQueueDTO queueDTO = orderQueueService.getByOpsOrderId(pauseReq.getOrderId()); - if (queueDTO != null) { - orderQueueService.pauseTask(queueDTO.getId()); - log.info("队列状态已更新为PAUSED: queueId={}, orderId={}", queueDTO.getId(), pauseReq.getOrderId()); - } - - // 3. 更新保洁扩展表:记录暂停开始时间 - OpsOrderCleanExtDO cleanExt = cleanExtMapper.selectByOpsOrderId(pauseReq.getOrderId()); - if (cleanExt != null) { - OpsOrderCleanExtDO updateObj = new OpsOrderCleanExtDO(); - updateObj.setId(cleanExt.getId()); - updateObj.setPauseStartTime(LocalDateTime.now()); - cleanExtMapper.updateById(updateObj); - } - - log.info("暂停保洁工单成功: orderId={}, cleanerId={}, reason={}", - pauseReq.getOrderId(), pauseReq.getCleanerId(), pauseReq.getPauseReason()); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void resumeCleanOrder(CleanOrderResumeReqDTO resumeReq) { - // 1. 调用基础服务恢复工单 - opsOrderService.resumeOrder(resumeReq.getOrderId(), resumeReq.getCleanerId()); - - // 2. 同步更新队列状态:PAUSED → DISPATCHED - OrderQueueDTO queueDTO = orderQueueService.getByOpsOrderId(resumeReq.getOrderId()); - if (queueDTO != null) { - orderQueueService.resumeTask(queueDTO.getId()); - log.info("队列状态已更新为DISPATCHED: queueId={}, orderId={}", queueDTO.getId(), resumeReq.getOrderId()); - } - - // 3. 更新保洁扩展表:计算暂停时长 - OpsOrderCleanExtDO cleanExt = cleanExtMapper.selectByOpsOrderId(resumeReq.getOrderId()); - if (cleanExt != null && cleanExt.getPauseStartTime() != null) { - long pauseSeconds = Duration.between(cleanExt.getPauseStartTime(), LocalDateTime.now()).getSeconds(); - - OpsOrderCleanExtDO updateObj = new OpsOrderCleanExtDO(); - updateObj.setId(cleanExt.getId()); - updateObj.setPauseEndTime(LocalDateTime.now()); - updateObj.setTotalPauseSeconds((cleanExt.getTotalPauseSeconds() != null ? cleanExt.getTotalPauseSeconds() : 0) + (int) pauseSeconds); - cleanExtMapper.updateById(updateObj); - } - - log.info("恢复保洁工单成功: orderId={}, cleanerId={}", resumeReq.getOrderId(), resumeReq.getCleanerId()); - } - - // ========== 优先级管理 ========== + // ==================== 优先级管理 ==================== @Override @Transactional(rollbackFor = Exception.class) @@ -399,72 +249,42 @@ public class CleanOrderServiceImpl implements CleanOrderService { order.setPriority(PriorityEnum.P0.getPriority()); opsOrderMapper.updateById(order); - // 4. 查询队列记录 + // 4. 更新队列优先级 OrderQueueDTO queueDTO = orderQueueService.getByOpsOrderId(orderId); - if (queueDTO == null) { - log.warn("工单不在队列中,无需处理插队: orderId={}", orderId); - return true; + if (queueDTO != null) { + orderQueueService.adjustPriority(queueDTO.getId(), PriorityEnum.P0, reason); + + // 5. 使用新的调度引擎处理P0紧急插队 + DispatchResult result = dispatchEngine.urgentInterrupt(orderId, queueDTO.getUserId()); + + // 6. 发送优先级升级通知 + cleanOrderEventHandler.sendPriorityUpgradeNotification(queueDTO.getUserId(), order.getOrderCode()); + + return result.isSuccess(); } - // 5. 更新队列优先级 - boolean updated = orderQueueService.adjustPriority(queueDTO.getId(), PriorityEnum.P0, reason); - if (!updated) { - log.error("更新队列优先级失败: queueId={}", queueDTO.getId()); - return false; - } - - // 6. 使用派单引擎处理P0紧急插队(可能打断当前任务) - boolean interruptHandled = dispatchEngineService.handlePriorityInterrupt(orderId); - - log.info("P0优先级升级完成: orderId={}, interruptHandled={}", orderId, interruptHandled); - return interruptHandled; + return true; } - // ========== 保洁特有的状态转换 ========== + // ==================== 保洁特有的状态转换 ==================== @Override @Transactional(rollbackFor = Exception.class) public void confirmOrder(Long orderId, Long cleanerId) { log.info("保洁员确认工单: orderId={}, cleanerId={}", orderId, cleanerId); - // 1. 查询工单 - OpsOrderDO order = opsOrderMapper.selectById(orderId); - if (order == null) { - log.error("工单不存在: orderId={}", orderId); - throw new IllegalArgumentException("工单不存在: " + orderId); - } + // 使用生命周期管理器转换状态 + OrderTransitionRequest request = OrderTransitionRequest.builder() + .orderId(orderId) + .targetStatus(WorkOrderStatusEnum.CONFIRMED) + .assigneeId(cleanerId) + .operatorType(OperatorTypeEnum.CLEANER) + .operatorId(cleanerId) + .reason("确认工单") + .build(); - // 2. 校验工单状态 - com.viewsh.module.ops.enums.WorkOrderStatusEnum currentStatus = - com.viewsh.module.ops.enums.WorkOrderStatusEnum.fromStatus(order.getStatus()); - if (!currentStatus.canConfirm()) { - log.error("工单状态不允许确认: orderId={}, status={}", orderId, currentStatus); - throw new IllegalStateException("只有已推送状态的工单可以确认,当前状态: " + currentStatus.getDescription()); - } - - // 3. 校验保洁员(是否是分配给自己的工单) - if (!order.getAssigneeId().equals(cleanerId)) { - log.error("保洁员不能确认不属于自己的工单: orderId={}, assigneeId={}, cleanerId={}", - orderId, order.getAssigneeId(), cleanerId); - throw new IllegalStateException("不能确认不属于自己的工单"); - } - - // 4. 使用状态机转换状态:DISPATCHED → CONFIRMED - orderStateMachine.transition( - order, - WorkOrderStatusEnum.CONFIRMED, - OperatorTypeEnum.CLEANER, - cleanerId, - "确认工单" - ); - - // 5. 保洁员确认后转为忙碌状态(监听器中处理,这里显式调用保证逻辑完整) - cleanerStatusService.updateStatus(cleanerId, CleanerStatusEnum.BUSY, "已确认工单"); - - log.info("确认工单成功: orderId={}, cleanerId={}, status=CONFIRMED, cleanerStatus=BUSY", orderId, cleanerId); - - // TODO: 推送通知给巡检员(保洁员已确认) - // TODO: 记录操作日志 + orderLifecycleManager.transition(request); + // 注意:保洁员状态更新由 CleanerStateChangeListener 处理 } @Override @@ -472,183 +292,84 @@ public class CleanOrderServiceImpl implements CleanOrderService { public void startWorkingOnBeacon(Long orderId, Long cleanerId, Long beaconId) { log.info("感知信标,开始作业: orderId={}, cleanerId={}, beaconId={}", orderId, cleanerId, beaconId); - // 1. 查询工单 - OpsOrderDO order = opsOrderMapper.selectById(orderId); - if (order == null) { - log.error("工单不存在: orderId={}", orderId); - throw new IllegalArgumentException("工单不存在: " + orderId); - } + // 使用生命周期管理器转换状态 + OrderTransitionRequest request = OrderTransitionRequest.builder() + .orderId(orderId) + .targetStatus(WorkOrderStatusEnum.ARRIVED) + .assigneeId(cleanerId) + .operatorType(OperatorTypeEnum.SYSTEM) + .operatorId(cleanerId) + .reason("感知信标,开始作业") + .build(); + request.putPayload("beaconId", beaconId); - // 2. 校验工单状态 - WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.valueOf(order.getStatus()); - if (!currentStatus.canStartWorking()) { - log.warn("工单状态不允许开始作业: orderId={}, status={}", orderId, currentStatus); - // 不抛异常,兼容某些场景 - return; - } + orderLifecycleManager.transition(request); - // 3. 使用状态机转换状态:CONFIRMED → ARRIVED - orderStateMachine.transition( - order, - WorkOrderStatusEnum.ARRIVED, - OperatorTypeEnum.SYSTEM, - cleanerId, - "感知信标,开始作业" - ); - - // 4. 更新保洁员状态为忙碌 - cleanerStatusService.updateStatus(cleanerId, CleanerStatusEnum.BUSY, "开始作业"); - - // 5. 记录到岗时间到保洁扩展表(状态机监听器会处理,这里显式调用保证逻辑完整) + // 记录到岗时间 recordArrivedTime(orderId); - - log.info("开始作业成功: orderId={}, cleanerId={}, status=ARRIVED", orderId, cleanerId); - - // TODO: 推送通知给巡检员(保洁员已到达作业现场) } @Override @Transactional(rollbackFor = Exception.class) public void autoCompleteOnSignalLost(Long orderId, Long cleanerId, Long beaconId, Integer lostSeconds) { - log.info("丢失信号,自动完成工单: orderId={}, cleanerId={}, beaconId={}, lostSeconds={}秒", - orderId, cleanerId, beaconId, lostSeconds); + log.info("丢失信号,自动完成工单: orderId={}, cleanerId={}, lostSeconds={}秒", + orderId, cleanerId, lostSeconds); - // 1. 查询工单 - OpsOrderDO order = opsOrderMapper.selectById(orderId); - if (order == null) { - log.error("工单不存在: orderId={}", orderId); - throw new IllegalArgumentException("工单不存在: " + orderId); - } + // 使用生命周期管理器转换状态 + OrderTransitionRequest request = OrderTransitionRequest.builder() + .orderId(orderId) + .targetStatus(WorkOrderStatusEnum.COMPLETED) + .assigneeId(cleanerId) + .operatorType(OperatorTypeEnum.SYSTEM) + .operatorId(cleanerId) + .reason("丢失信号,自动完成") + .build(); + request.putPayload("lostSeconds", lostSeconds); + request.putPayload("beaconId", beaconId); - // 2. 校验工单状态 - WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.valueOf(order.getStatus()); - if (!currentStatus.canComplete()) { - log.warn("工单状态不允许完成: orderId={}, status={}", orderId, currentStatus); - return; - } + orderLifecycleManager.transition(request); - // 3. 使用状态机转换状态:ARRIVED → COMPLETED - // 状态机会自动记录结束时间 - orderStateMachine.transition( - order, - WorkOrderStatusEnum.COMPLETED, - OperatorTypeEnum.SYSTEM, - cleanerId, - "丢失信号,自动完成" - ); + // 记录完成时间 + recordCompletedTime(orderId); - // 4. 同步更新队列状态:DISPATCHED → COMPLETED - OrderQueueDTO queueDTO = orderQueueService.getByOpsOrderId(orderId); - if (queueDTO != null) { - orderQueueService.updateStatus(queueDTO.getId(), com.viewsh.module.ops.enums.OrderQueueStatusEnum.COMPLETED); - log.info("队列状态已更新为COMPLETED: queueId={}, orderId={}", queueDTO.getId(), orderId); - } - - // 5. 记录完成时间到保洁扩展表(状态机监听器会处理) - calculateDuration(orderId); - - log.info("工单自动完成: orderId={}, cleanerId={}, duration={}秒", - orderId, cleanerId, order.getCompletionSeconds()); - - // 6. 推送通知给巡检员(工单已完成,待验收) - notifyInspectorsOrderCompleted(orderId, cleanerId); - - // 7. 自动推送队列中的下一个任务(状态机监听器会处理,这里显式调用保证逻辑完整) - autoDispatchNextOrder(orderId, cleanerId); - } - - /** - * 推送通知给巡检员(工单已完成,待验收) - * 场景:保洁员完成任务后,通知巡检员进行验收 - * - * @param orderId 工单ID - * @param cleanerId 保洁员ID - */ - private void notifyInspectorsOrderCompleted(Long orderId, Long cleanerId) { - // TODO: 下一阶段实现 - // 1. 查询工单所属区域 - // 2. 查询该区域的巡检员列表 - // 3. 推送消息给巡检员(IoT工牌、App推送等) - // 4. 消息内容:保洁工单XXX已完成,请前往验收 - - log.info("推送验收通知给巡检员: orderId={}, cleanerId={}", orderId, cleanerId); - - // 示例代码(下一阶段实现): - // OpsOrderDO order = opsOrderMapper.selectById(orderId); - // List inspectors = cleanerStatusService.listInspectorsByArea(order.getAreaId()); - // for (OpsCleanerStatusDO inspector : inspectors) { - // iotDeviceService.pushNotification(inspector.getDeviceId(), - // String.format("保洁工单%s已完成,请前往%s验收", order.getOrderCode(), order.getLocation())); - // } + // 自动推送下一个任务 + dispatchEngine.autoDispatchNext(orderId, cleanerId); } @Override @Transactional(rollbackFor = Exception.class) public void autoDispatchNextOrder(Long completedOrderId, Long cleanerId) { - log.info("推送下一个任务: completedOrderId={}, cleanerId={}", completedOrderId, cleanerId); + log.info("任务完成后自动调度下一个: completedOrderId={}, cleanerId={}", completedOrderId, cleanerId); - // 1. 查询队列中等待的任务 - List queuedTasks = orderQueueService.getWaitingTasksByUserId(cleanerId); + // 使用新的调度引擎自动推送下一个 + DispatchResult result = dispatchEngine.autoDispatchNext(completedOrderId, cleanerId); - if (queuedTasks.isEmpty()) { - // 没有待办任务,保洁员变空闲 - cleanerStatusService.updateStatus(cleanerId, CleanerStatusEnum.IDLE, "所有任务已完成"); - cleanerStatusService.clearCurrentWorkOrder(cleanerId); - log.info("保洁员所有任务已完成,转为空闲状态: cleanerId={}", cleanerId); - return; + if (result.isSuccess()) { + log.info("已自动推送下一个任务: cleanerId={}", cleanerId); + } else { + log.info("无等待任务,保洁员变空闲: cleanerId={}", cleanerId); + // 状态更新由 CleanerStateChangeListener 处理 } - - // 2. 推送队列中的第一个任务 - OrderQueueDTO nextTask = queuedTasks.get(0); - dispatchToCleaner(nextTask.getId()); - - // 3. 语音播报 - int queueCount = queuedTasks.size(); - OpsOrderDO nextOrder = opsOrderMapper.selectById(nextTask.getOpsOrderId()); - String nextTaskTitle = nextOrder != null ? nextOrder.getTitle() : "未知任务"; - playVoiceForNextTask(cleanerId, queueCount, nextTaskTitle); - - log.info("已自动推送下一个任务: taskId={}, orderId={}, queueCount={}", - nextTask.getId(), nextTask.getOpsOrderId(), queueCount); } - // ========== 语音播报方法(支持去重合并) ========== + // ==================== 语音播报(委托给 EventHandler)==================== @Override public void playVoiceForNewOrder(Long cleanerId) { - log.info("语音播报:空闲保洁员收到新工单: cleanerId={}", cleanerId); - - // 空闲保洁员收到新工单,立即播报(不合并) - // TODO: 调用 IoT 设备服务进行语音播报 - // iotDeviceService.playVoice(cleanerId, "您有1条新的待办工单"); - - // TODO: 调用 IoT 设备服务进行震动提醒 - // iotDeviceService.vibrate(cleanerId, 1000); + cleanOrderEventHandler.sendNewOrderNotification(cleanerId, null); } @Override public void playVoiceForQueuedOrder(Long cleanerId, int queueCount) { - log.info("语音播报:忙碌保洁员收到待办(合并播报): cleanerId={}, queueCount={}", cleanerId, queueCount); - - // 忙碌保洁员收到新工单,使用去重服务合并播报 - voiceBroadcastDeduplicationService.recordAndBroadcast(cleanerId, 1, false); + cleanOrderEventHandler.sendQueuedOrderNotification(cleanerId, queueCount); } @Override public void playVoiceForNextTask(Long cleanerId, int queueCount, String nextTaskTitle) { - log.info("语音播报:任务完成后自动推送下一个: cleanerId={}, queueCount={}, taskTitle={}", - cleanerId, queueCount, nextTaskTitle); - - // 任务完成后自动推送下一个,立即播报(不合并) - // TODO: 调用 IoT 设备服务进行语音播报 - String message = String.format("待办工单总数%d个,第一位待办工单%s", queueCount, nextTaskTitle); - // iotDeviceService.playVoice(cleanerId, message); - - // TODO: 震动提醒 - // iotDeviceService.vibrate(cleanerId, 1000); + cleanOrderEventHandler.sendNextTaskNotification(cleanerId, queueCount, nextTaskTitle); } - // ========== 作业时长计算 ========== + // ==================== 作业时长计算 ==================== @Override public Integer calculateActualDuration(Long orderId) { @@ -657,46 +378,42 @@ public class CleanOrderServiceImpl implements CleanOrderService { return 0; } - // 计算实际作业时长(秒) long actualSeconds = Duration.between(cleanExt.getArrivedTime(), cleanExt.getCompletedTime()).getSeconds(); - - // 减去累计暂停时长 int totalPauseSeconds = cleanExt.getTotalPauseSeconds() != null ? cleanExt.getTotalPauseSeconds() : 0; long workSeconds = actualSeconds - totalPauseSeconds; return (int) Math.max(workSeconds, 0); } - @Override - @Transactional(rollbackFor = Exception.class) - public void recordArrivedTime(Long orderId) { - // 更新保洁扩展表:记录到岗时间 + // ==================== 私有方法 ==================== + + /** + * 记录到岗时间 + * 注意:此方法在被 @Transactional 的公共方法调用时,会参与同一事务 + */ + private void recordArrivedTime(Long orderId) { OpsOrderCleanExtDO cleanExt = cleanExtMapper.selectByOpsOrderId(orderId); if (cleanExt != null) { OpsOrderCleanExtDO updateObj = new OpsOrderCleanExtDO(); updateObj.setId(cleanExt.getId()); updateObj.setArrivedTime(LocalDateTime.now()); cleanExtMapper.updateById(updateObj); - - log.info("记录到岗时间: orderId={}", orderId); + log.debug("记录到岗时间: orderId={}", orderId); } } - @Override - @Transactional(rollbackFor = Exception.class) - public void calculateDuration(Long orderId) { - // 更新保洁扩展表:记录完成时间 + /** + * 记录完成时间 + * 注意:此方法在被 @Transactional 的公共方法调用时,会参与同一事务 + */ + private void recordCompletedTime(Long orderId) { OpsOrderCleanExtDO cleanExt = cleanExtMapper.selectByOpsOrderId(orderId); if (cleanExt != null) { OpsOrderCleanExtDO updateObj = new OpsOrderCleanExtDO(); updateObj.setId(cleanExt.getId()); updateObj.setCompletedTime(LocalDateTime.now()); cleanExtMapper.updateById(updateObj); - - // 计算实际作业时长 - Integer actualDuration = calculateActualDuration(orderId); - log.info("工单完成: orderId={}, actualDuration={}秒", orderId, actualDuration); + log.debug("记录完成时间: orderId={}", orderId); } } - }