chore: 【ops】保洁工单基础Service
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
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. 并发控制和去重合并播报
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public interface CleanOrderService {
|
||||
|
||||
// ========== 工单创建与队列管理 ==========
|
||||
|
||||
/**
|
||||
* 自动创建保洁工单
|
||||
* 场景:定时任务、系统触发、IoT设备触发
|
||||
*
|
||||
* @param createReq 创建请求
|
||||
* @return 工单ID
|
||||
*/
|
||||
Long createAutoCleanOrder(CleanOrderAutoCreateReqDTO createReq);
|
||||
|
||||
/**
|
||||
* 工单入队(不推送,状态:PENDING → QUEUED)
|
||||
* 用于忙碌保洁员收到新工单时
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param cleanerId 保洁员ID
|
||||
*/
|
||||
void enqueueOrderOnly(Long orderId, Long cleanerId);
|
||||
|
||||
/**
|
||||
* 入队并立即推送(空闲保洁员,状态:PENDING → QUEUED → DISPATCHED)
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param cleanerId 保洁员ID
|
||||
*/
|
||||
void enqueueAndDispatch(Long orderId, Long cleanerId);
|
||||
|
||||
/**
|
||||
* 推送工单到工牌(状态:QUEUED → DISPATCHED)
|
||||
* 支持并发控制,防止重复推送
|
||||
*
|
||||
* @param queueId 队列ID
|
||||
*/
|
||||
void dispatchToCleaner(Long queueId);
|
||||
|
||||
// ========== 保洁特有的状态转换 ==========
|
||||
|
||||
/**
|
||||
* 保洁员按键确认工单
|
||||
* 状态转换:DISPATCHED → CONFIRMED
|
||||
* 同时更新保洁员状态:IDLE → BUSY(关键:确认后才变为忙碌)
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param cleanerId 保洁员ID
|
||||
*/
|
||||
void confirmOrder(Long orderId, Long cleanerId);
|
||||
|
||||
/**
|
||||
* 感知信标,开始作业
|
||||
* 状态转换:CONFIRMED → ARRIVED
|
||||
*
|
||||
* 注意:此方法由IoT信标事件触发,暂不实现,留接口
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param cleanerId 保洁员ID
|
||||
* @param beaconId 信标ID
|
||||
*/
|
||||
void startWorkingOnBeacon(Long orderId, Long cleanerId, Long beaconId);
|
||||
|
||||
/**
|
||||
* 丢失信号,自动完成
|
||||
* 状态转换:ARRIVED → COMPLETED
|
||||
* 完成后自动推送队列中的下一个任务
|
||||
*
|
||||
* 注意:此方法由IoT信标事件触发,暂不实现,留接口
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param cleanerId 保洁员ID
|
||||
* @param beaconId 信标ID
|
||||
* @param lostSeconds 丢失信号时长(秒)
|
||||
*/
|
||||
void autoCompleteOnSignalLost(Long orderId, Long cleanerId, Long beaconId, Integer lostSeconds);
|
||||
|
||||
// ========== 任务自动切换 ==========
|
||||
|
||||
/**
|
||||
* 任务完成后,自动推送队列中的下一个任务
|
||||
*
|
||||
* @param completedOrderId 已完成的工单ID
|
||||
* @param cleanerId 保洁员ID
|
||||
*/
|
||||
void autoDispatchNextOrder(Long completedOrderId, Long cleanerId);
|
||||
|
||||
// ========== 语音播报(支持去重合并) ==========
|
||||
|
||||
/**
|
||||
* 语音播报:空闲保洁员收到新工单
|
||||
*
|
||||
* @param cleanerId 保洁员ID
|
||||
*/
|
||||
void playVoiceForNewOrder(Long cleanerId);
|
||||
|
||||
/**
|
||||
* 语音播报:忙碌保洁员收到待办(支持去重合并)
|
||||
* 例如:"新增3项待办,您共有5个待办工单"
|
||||
*
|
||||
* @param cleanerId 保洁员ID
|
||||
* @param queueCount 当前待办数量
|
||||
*/
|
||||
void playVoiceForQueuedOrder(Long cleanerId, int queueCount);
|
||||
|
||||
/**
|
||||
* 语音播报:任务完成后自动推送下一个
|
||||
* 例如:"待办工单总数2个,第一位待办工单xxx"
|
||||
*
|
||||
* @param cleanerId 保洁员ID
|
||||
* @param queueCount 待办数量
|
||||
* @param nextTaskTitle 下一个任务标题
|
||||
*/
|
||||
void playVoiceForNextTask(Long cleanerId, int queueCount, String nextTaskTitle);
|
||||
|
||||
// ========== 暂停/恢复(调用通用服务)==========
|
||||
|
||||
/**
|
||||
* 暂停保洁工单(状态转换:ARRIVED -> PAUSED)
|
||||
* 场景:P0紧急工单插队、临时离开
|
||||
*
|
||||
* @param pauseReq 暂停请求
|
||||
*/
|
||||
void pauseCleanOrder(CleanOrderPauseReqDTO pauseReq);
|
||||
|
||||
// ========== 优先级管理 ==========
|
||||
/**
|
||||
* 升级工单优先级为P0并处理紧急插队
|
||||
* 场景:巡检员手动将普通工单升级为P0紧急任务
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param reason 升级原因
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean upgradePriorityToP0(Long orderId, String reason);
|
||||
|
||||
/**
|
||||
* 恢复保洁工单(状态转换:PAUSED -> ARRIVED)
|
||||
*
|
||||
* @param resumeReq 恢复请求
|
||||
*/
|
||||
void resumeCleanOrder(CleanOrderResumeReqDTO resumeReq);
|
||||
|
||||
// ========== 作业时长计算 ==========
|
||||
|
||||
/**
|
||||
* 计算实际作业时长(秒)
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @return 作业时长(秒)
|
||||
*/
|
||||
Integer calculateActualDuration(Long orderId);
|
||||
|
||||
/**
|
||||
* 记录到岗时间
|
||||
* 场景:状态机监听器调用
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
*/
|
||||
void recordArrivedTime(Long orderId);
|
||||
|
||||
/**
|
||||
* 计算并更新作业时长
|
||||
* 场景:工单完成时,状态机监听器调用
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
*/
|
||||
void calculateDuration(Long orderId);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,702 @@
|
||||
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.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.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.service.order.OpsOrderService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 保洁工单扩展服务实现
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CleanOrderServiceImpl implements CleanOrderService {
|
||||
|
||||
@Resource
|
||||
private OpsOrderService opsOrderService;
|
||||
|
||||
@Resource
|
||||
private OpsOrderMapper opsOrderMapper;
|
||||
|
||||
@Resource
|
||||
private OpsOrderCleanExtMapper cleanExtMapper;
|
||||
|
||||
@Resource
|
||||
private OrderQueueService orderQueueService;
|
||||
|
||||
@Resource
|
||||
private CleanerAreaPriorityStrategy cleanerAreaPriorityStrategy;
|
||||
|
||||
@Resource
|
||||
private CleanerStatusService cleanerStatusService;
|
||||
|
||||
@Resource
|
||||
private VoiceBroadcastDeduplicationService voiceBroadcastDeduplicationService;
|
||||
|
||||
@Resource
|
||||
private OrderStateMachine orderStateMachine;
|
||||
|
||||
@Resource
|
||||
private DispatchEngineService dispatchEngineService;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createAutoCleanOrder(CleanOrderAutoCreateReqDTO createReq) {
|
||||
// 1. 调用基础服务创建工单
|
||||
Long orderId = opsOrderService.createOrder(createReq);
|
||||
|
||||
// 2. 创建保洁扩展信息
|
||||
OpsOrderCleanExtDO cleanExt = OpsOrderCleanExtDO.builder()
|
||||
.opsOrderId(orderId)
|
||||
.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()));
|
||||
|
||||
return orderId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 工单入队(根据保洁员状态选择不同策略)
|
||||
* 推荐保洁员、入队、派单、同步状态
|
||||
*
|
||||
* @param orderId 工单ID
|
||||
* @param areaId 区域ID
|
||||
* @param priority 优先级
|
||||
*/
|
||||
private void enqueueOrder(Long orderId, Long areaId, PriorityEnum priority) {
|
||||
try {
|
||||
// 1. 推荐保洁员
|
||||
Long recommendedCleanerId = cleanerAreaPriorityStrategy.recommendCleanerForNewOrder(areaId, priority);
|
||||
|
||||
if (recommendedCleanerId == null) {
|
||||
log.warn("未找到可用的保洁员,工单将无法自动派单: orderId={}, areaId={}", orderId, areaId);
|
||||
// 工单保持在PENDING状态,等待后续处理
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 查询保洁员状态
|
||||
OpsCleanerStatusDO cleanerStatusDO =
|
||||
cleanerStatusService.getStatus(recommendedCleanerId);
|
||||
|
||||
if (cleanerStatusDO == null) {
|
||||
log.warn("保洁员状态不存在,无法派单: cleanerId={}", recommendedCleanerId);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
log.warn("保洁员状态不可接单: cleanerId={}, status={}",
|
||||
recommendedCleanerId, cleanerStatus);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("工单入队或派单失败: orderId={}, areaId={}", orderId, areaId, e);
|
||||
// TODO: 可以考虑回滚工单创建,或者标记为"入队失败"状态
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 队列推送机制实现 ==========
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void enqueueOrderOnly(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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void enqueueAndDispatch(Long orderId, Long cleanerId) {
|
||||
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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
} else {
|
||||
// 普通任务:正常派单
|
||||
dispatchToCleaner(queueId);
|
||||
}
|
||||
|
||||
// 5. 语音播报
|
||||
playVoiceForNewOrder(cleanerId);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查保洁员是否已有任务在推送中(状态为DISPATCHED)
|
||||
* 用于并发控制,防止重复推送
|
||||
*/
|
||||
private boolean checkIfHasDispatchingTask(Long cleanerId) {
|
||||
// 查询该保洁员是否有状态为 DISPATCHED 的队列记录
|
||||
List<OrderQueueDTO> 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)
|
||||
public boolean upgradePriorityToP0(Long orderId, String reason) {
|
||||
log.warn("升级工单优先级为P0: orderId={}, reason={}", orderId, reason);
|
||||
|
||||
// 1. 查询工单
|
||||
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
log.error("工单不存在: orderId={}", orderId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. 检查当前优先级
|
||||
PriorityEnum currentPriority = PriorityEnum.fromPriority(order.getPriority());
|
||||
if (currentPriority == PriorityEnum.P0) {
|
||||
log.info("工单已经是P0优先级,无需升级: orderId={}", orderId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 3. 更新工单优先级
|
||||
order.setPriority(PriorityEnum.P0.getPriority());
|
||||
opsOrderMapper.updateById(order);
|
||||
|
||||
// 4. 查询队列记录
|
||||
OrderQueueDTO queueDTO = orderQueueService.getByOpsOrderId(orderId);
|
||||
if (queueDTO == null) {
|
||||
log.warn("工单不在队列中,无需处理插队: orderId={}", orderId);
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// ========== 保洁特有的状态转换 ==========
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
// 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: 记录操作日志
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
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);
|
||||
}
|
||||
|
||||
// 2. 校验工单状态
|
||||
WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.valueOf(order.getStatus());
|
||||
if (!currentStatus.canStartWorking()) {
|
||||
log.warn("工单状态不允许开始作业: orderId={}, status={}", orderId, currentStatus);
|
||||
// 不抛异常,兼容某些场景
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
// 1. 查询工单
|
||||
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
log.error("工单不存在: orderId={}", orderId);
|
||||
throw new IllegalArgumentException("工单不存在: " + orderId);
|
||||
}
|
||||
|
||||
// 2. 校验工单状态
|
||||
WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.valueOf(order.getStatus());
|
||||
if (!currentStatus.canComplete()) {
|
||||
log.warn("工单状态不允许完成: orderId={}, status={}", orderId, currentStatus);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. 使用状态机转换状态:ARRIVED → COMPLETED
|
||||
// 状态机会自动记录结束时间
|
||||
orderStateMachine.transition(
|
||||
order,
|
||||
WorkOrderStatusEnum.COMPLETED,
|
||||
OperatorTypeEnum.SYSTEM,
|
||||
cleanerId,
|
||||
"丢失信号,自动完成"
|
||||
);
|
||||
|
||||
// 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<OpsCleanerStatusDO> inspectors = cleanerStatusService.listInspectorsByArea(order.getAreaId());
|
||||
// for (OpsCleanerStatusDO inspector : inspectors) {
|
||||
// iotDeviceService.pushNotification(inspector.getDeviceId(),
|
||||
// String.format("保洁工单%s已完成,请前往%s验收", order.getOrderCode(), order.getLocation()));
|
||||
// }
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void autoDispatchNextOrder(Long completedOrderId, Long cleanerId) {
|
||||
log.info("推送下一个任务: completedOrderId={}, cleanerId={}", completedOrderId, cleanerId);
|
||||
|
||||
// 1. 查询队列中等待的任务
|
||||
List<OrderQueueDTO> queuedTasks = orderQueueService.getWaitingTasksByUserId(cleanerId);
|
||||
|
||||
if (queuedTasks.isEmpty()) {
|
||||
// 没有待办任务,保洁员变空闲
|
||||
cleanerStatusService.updateStatus(cleanerId, CleanerStatusEnum.IDLE, "所有任务已完成");
|
||||
cleanerStatusService.clearCurrentWorkOrder(cleanerId);
|
||||
log.info("保洁员所有任务已完成,转为空闲状态: cleanerId={}", cleanerId);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// ========== 语音播报方法(支持去重合并) ==========
|
||||
|
||||
@Override
|
||||
public void playVoiceForNewOrder(Long cleanerId) {
|
||||
log.info("语音播报:空闲保洁员收到新工单: cleanerId={}", cleanerId);
|
||||
|
||||
// 空闲保洁员收到新工单,立即播报(不合并)
|
||||
// TODO: 调用 IoT 设备服务进行语音播报
|
||||
// iotDeviceService.playVoice(cleanerId, "您有1条新的待办工单");
|
||||
|
||||
// TODO: 调用 IoT 设备服务进行震动提醒
|
||||
// iotDeviceService.vibrate(cleanerId, 1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void playVoiceForQueuedOrder(Long cleanerId, int queueCount) {
|
||||
log.info("语音播报:忙碌保洁员收到待办(合并播报): cleanerId={}, queueCount={}", cleanerId, queueCount);
|
||||
|
||||
// 忙碌保洁员收到新工单,使用去重服务合并播报
|
||||
voiceBroadcastDeduplicationService.recordAndBroadcast(cleanerId, 1, false);
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
// ========== 作业时长计算 ==========
|
||||
|
||||
@Override
|
||||
public Integer calculateActualDuration(Long orderId) {
|
||||
OpsOrderCleanExtDO cleanExt = cleanExtMapper.selectByOpsOrderId(orderId);
|
||||
if (cleanExt == null || cleanExt.getArrivedTime() == null || cleanExt.getCompletedTime() == null) {
|
||||
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) {
|
||||
// 更新保洁扩展表:记录到岗时间
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void calculateDuration(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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user