feat(ops): 手动派单遵循执行人忙碌状态,忙碌时入队等待

ManualOrderActionFacade.dispatch:
- 新增 strategy.isAssigneeIdle() 判断,空闲→DISPATCHED,忙碌→QUEUED
- 不再无条件直接派发

OrderBusinessStrategy:
- 新增 isAssigneeIdle() 默认方法,默认返回 true

CleanOrderBusinessStrategy:
- isAssigneeIdle 通过 BadgeDeviceStatusService.isBusy() 判断设备忙碌

SecurityOrderBusinessStrategy:
- isAssigneeIdle 通过 UserDispatchStatusService.isIdle() 判断人员忙碌

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-03-27 16:37:30 +08:00
parent 78396cf35a
commit 55ef659364
4 changed files with 39 additions and 8 deletions

View File

@@ -36,6 +36,12 @@ public class CleanOrderBusinessStrategy implements OrderBusinessStrategy {
return WorkOrderTypeEnum.CLEAN.getType().equals(businessType);
}
@Override
public boolean isAssigneeIdle(DispatchOrderCommand cmd, OpsOrderDO order) {
BadgeDeviceStatusDTO badge = badgeDeviceStatusService.getBadgeStatus(cmd.getAssigneeId());
return badge != null && !badge.isBusy();
}
@Override
public void validateDispatch(DispatchOrderCommand cmd, OpsOrderDO order) {
if (cmd.getAssigneeId() == null) {

View File

@@ -68,10 +68,14 @@ public class ManualOrderActionFacade {
OrderBusinessStrategy strategy = resolveStrategy(cmd.getBusinessType());
strategy.validateDispatch(cmd, order);
// 3. 状态变更
// 3. 判断执行人是否空闲,决定目标状态
boolean idle = strategy.isAssigneeIdle(cmd, order);
WorkOrderStatusEnum targetStatus = idle ? WorkOrderStatusEnum.DISPATCHED : WorkOrderStatusEnum.QUEUED;
// 4. 状态变更
OrderTransitionRequest request = OrderTransitionRequest.builder()
.orderId(cmd.getOrderId())
.targetStatus(WorkOrderStatusEnum.DISPATCHED)
.targetStatus(targetStatus)
.assigneeId(cmd.getAssigneeId())
.assigneeName(cmd.getAssigneeName())
.assigneePhone(cmd.getAssigneePhone())
@@ -85,18 +89,19 @@ public class ManualOrderActionFacade {
throw new IllegalStateException("手动派单失败: " + result.getMessage());
}
// 4. 更新主表执行人(只更新 assignee 字段,避免覆盖状态机已写入的 status
// 5. 更新主表执行人(只更新 assignee 字段,避免覆盖状态机已写入的 status
OpsOrderDO assigneeUpdate = new OpsOrderDO();
assigneeUpdate.setId(cmd.getOrderId());
assigneeUpdate.setAssigneeId(cmd.getAssigneeId());
assigneeUpdate.setAssigneeName(cmd.getAssigneeName());
opsOrderMapper.updateById(assigneeUpdate);
// 5. 条线后置
// 6. 条线后置
// 注:业务日志由生命周期事件 → 条线 EventListener 统一记录,此处不重复写
strategy.afterDispatch(cmd, order);
log.info("[ManualOrderActionFacade] 手动派单完成: orderId={}, assigneeId={}", cmd.getOrderId(), cmd.getAssigneeId());
log.info("[ManualOrderActionFacade] 手动派单完成: orderId={}, assigneeId={}, targetStatus={}",
cmd.getOrderId(), cmd.getAssigneeId(), targetStatus);
}
// ==================== 手动升级优先级 ====================

View File

@@ -20,6 +20,20 @@ public interface OrderBusinessStrategy {
*/
boolean supports(String businessType);
// ==================== 派单决策 ====================
/**
* 判断执行人是否空闲,决定手动派单的目标状态。
* <p>
* 空闲返回 true → DISPATCHED直接派发
* 忙碌返回 false → QUEUED入队等待
* <p>
* 默认返回 true直接派发条线按需覆写。
*/
default boolean isAssigneeIdle(DispatchOrderCommand cmd, OpsOrderDO order) {
return true;
}
// ==================== 前置校验 ====================
default void validateDispatch(DispatchOrderCommand cmd, OpsOrderDO order) {}

View File

@@ -8,13 +8,11 @@ import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
import com.viewsh.module.ops.enums.WorkOrderTypeEnum;
import com.viewsh.module.ops.security.dal.dataobject.area.OpsAreaSecurityUserDO;
import com.viewsh.module.ops.security.dal.mysql.area.OpsAreaSecurityUserMapper;
import com.viewsh.module.ops.service.dispatch.UserDispatchStatusService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import static com.viewsh.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.viewsh.module.ops.enums.ErrorCodeConstants.SECURITY_ASSIGNEE_NOT_BOUND_TO_AREA;
/**
* 安保条线策略
* <p>
@@ -32,11 +30,19 @@ public class SecurityOrderBusinessStrategy implements OrderBusinessStrategy {
@Resource
private OrderQueueService orderQueueService;
@Resource
private UserDispatchStatusService userDispatchStatusService;
@Override
public boolean supports(String businessType) {
return WorkOrderTypeEnum.SECURITY.getType().equals(businessType);
}
@Override
public boolean isAssigneeIdle(DispatchOrderCommand cmd, OpsOrderDO order) {
return userDispatchStatusService.isIdle(cmd.getAssigneeId());
}
@Override
public void validateDispatch(DispatchOrderCommand cmd, OpsOrderDO order) {
// 尝试从区域绑定记录回填姓名(不做区域限制,允许跨区域派单)