feat(ops): 新增手动操作枚举与模型定义
引入统一手动动作基础设施: - ManualActionTypeEnum: 手动动作类型(创建/派单/取消/完单/升级) - OrderActionSourceEnum: 动作来源(管理后台/开放接口) - OrderAuditPayloadKeys: 审计 payload 标准化 key - OrderEventTypeEnum: 事件类型枚举值对齐状态机(DISPATCHED/QUEUED/CONFIRMED) - OperatorContext: 统一操作人上下文 - *Command: 手动动作命令模型(Dispatch/Cancel/Complete/UpgradePriority) - OrderBusinessStrategy: 条线策略接口 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
package com.viewsh.module.ops.environment.service.cleanorder.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 保洁手动取消工单 Request DTO
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Schema(description = "管理后台 - 保洁手动取消工单 Request DTO")
|
||||
@Data
|
||||
public class CleanManualCancelReqDTO {
|
||||
|
||||
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "工单ID不能为空")
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "取消原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "误报,无需保洁")
|
||||
@NotBlank(message = "取消原因不能为空")
|
||||
private String reason;
|
||||
|
||||
@Schema(description = "操作人ID(由 Controller 注入)", hidden = true)
|
||||
private Long operatorId;
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.viewsh.module.ops.environment.service.cleanorder.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 保洁手动创建工单 Request DTO
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Schema(description = "管理后台 - 保洁手动创建工单 Request DTO")
|
||||
@Data
|
||||
public class CleanManualCreateReqDTO {
|
||||
|
||||
@Schema(description = "工单标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "三楼电梯厅紧急清洁")
|
||||
@NotBlank(message = "工单标题不能为空")
|
||||
private String title;
|
||||
|
||||
@Schema(description = "工单描述", example = "地面有大面积水渍")
|
||||
private String description;
|
||||
|
||||
@Schema(description = "优先级(0=P0紧急, 1=P1重要, 2=P2普通)", example = "1")
|
||||
private Integer priority;
|
||||
|
||||
@Schema(description = "区域ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
|
||||
@NotNull(message = "区域ID不能为空")
|
||||
private Long areaId;
|
||||
|
||||
@Schema(description = "保洁类型", example = "SPOT")
|
||||
private String cleaningType;
|
||||
|
||||
@Schema(description = "预计作业时长(分钟)", example = "30")
|
||||
private Integer expectedDuration;
|
||||
|
||||
@Schema(description = "操作人ID(由 Controller 注入)", hidden = true)
|
||||
private Long operatorId;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.viewsh.module.ops.environment.service.cleanorder.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 管理后台 - 保洁手动派单 Request DTO
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Schema(description = "管理后台 - 保洁手动派单 Request DTO")
|
||||
@Data
|
||||
public class CleanManualDispatchReqDTO {
|
||||
|
||||
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "工单ID不能为空")
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "目标设备ID(工牌设备ID)", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "目标设备ID不能为空")
|
||||
private Long assigneeId;
|
||||
|
||||
@Schema(description = "派单备注", example = "紧急情况,指定该设备处理")
|
||||
private String remark;
|
||||
|
||||
@Schema(description = "操作人ID(由 Controller 注入)", hidden = true)
|
||||
private Long operatorId;
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.viewsh.module.ops.environment.service.manual;
|
||||
|
||||
import com.viewsh.module.ops.api.queue.OrderQueueService;
|
||||
import com.viewsh.module.ops.core.manual.model.*;
|
||||
import com.viewsh.module.ops.core.manual.strategy.OrderBusinessStrategy;
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||
import com.viewsh.module.ops.enums.WorkOrderTypeEnum;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 保洁条线策略
|
||||
* <p>
|
||||
* 处理保洁特有的前置校验和后置副作用。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CleanOrderBusinessStrategy implements OrderBusinessStrategy {
|
||||
|
||||
@Resource
|
||||
private OrderQueueService orderQueueService;
|
||||
|
||||
@Override
|
||||
public boolean supports(String businessType) {
|
||||
return WorkOrderTypeEnum.CLEAN.getType().equals(businessType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterUpgradePriority(UpgradePriorityCommand cmd, OpsOrderDO order) {
|
||||
// 如果工单在队列中,触发队列分数重算
|
||||
if (WorkOrderStatusEnum.QUEUED.getStatus().equals(order.getStatus()) && order.getAssigneeId() != null) {
|
||||
orderQueueService.rebuildWaitingTasksByUserId(order.getAssigneeId(), order.getAreaId());
|
||||
log.info("[CleanStrategy] 升级优先级后重算队列: orderId={}, assigneeId={}",
|
||||
cmd.getOrderId(), order.getAssigneeId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package com.viewsh.module.ops.environment.service.notification;
|
||||
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
|
||||
import com.viewsh.module.ops.environment.constants.CleanNotificationConstants;
|
||||
import com.viewsh.module.ops.environment.service.voice.TtsQueueMessage;
|
||||
import com.viewsh.module.ops.environment.service.voice.VoiceBroadcastService;
|
||||
import com.viewsh.module.system.api.notify.NotifyMessageSendApi;
|
||||
import com.viewsh.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 保洁工单通知服务。
|
||||
* <p>
|
||||
* 负责语音播报和站内信发送,供事件监听器、业务服务、策略层复用,
|
||||
* 避免业务层反向依赖事件监听器形成循环依赖。
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class CleanOrderNotificationService {
|
||||
|
||||
@Resource
|
||||
private OpsOrderMapper opsOrderMapper;
|
||||
|
||||
@Resource
|
||||
private VoiceBroadcastService voiceBroadcastService;
|
||||
|
||||
@Resource
|
||||
private NotifyMessageSendApi notifyMessageSendApi;
|
||||
|
||||
@Async("ops-task-executor")
|
||||
public void sendNewOrderNotification(Long deviceId, Long orderId) {
|
||||
try {
|
||||
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
log.warn("[新工单通知] 工单不存在: orderId={}", orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[新工单通知] deviceId={}, orderId={}", deviceId, orderId);
|
||||
voiceBroadcastService.broadcastLoop(deviceId,
|
||||
CleanNotificationConstants.VoiceTemplate.NEW_ORDER_RING, orderId);
|
||||
|
||||
sendNotifyMessage(1L,
|
||||
CleanNotificationConstants.TemplateCode.NEW_ORDER,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.newOrderParams(
|
||||
order.getOrderCode(),
|
||||
order.getTitle(),
|
||||
CleanNotificationConstants.VoiceBuilder.getAreaName(order.getLocation())));
|
||||
} catch (Exception e) {
|
||||
log.error("[新工单通知] 发送失败: deviceId={}, orderId={}", deviceId, orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Async("ops-task-executor")
|
||||
public void sendQueuedOrderNotification(Long deviceId, int queueCount, Long orderId) {
|
||||
try {
|
||||
log.info("[待办增加通知] deviceId={}, queueCount={}", deviceId, queueCount);
|
||||
|
||||
String voiceMessage = CleanNotificationConstants.VoiceBuilder.buildQueuedOrder(queueCount);
|
||||
playVoice(deviceId, voiceMessage, orderId);
|
||||
|
||||
if (queueCount >= 3) {
|
||||
sendNotifyMessage(1L,
|
||||
CleanNotificationConstants.TemplateCode.QUEUED_ORDER,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.queuedOrderParams(queueCount, 1));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("[待办增加通知] 发送失败: deviceId={}", deviceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Async("ops-task-executor")
|
||||
public void sendNextTaskNotification(Long deviceId, int queueCount, String orderTitle) {
|
||||
try {
|
||||
log.info("[下一任务通知] deviceId={}, queueCount={}, title={}", deviceId, queueCount, orderTitle);
|
||||
|
||||
String voiceMessage = CleanNotificationConstants.VoiceBuilder.buildNextTask(orderTitle);
|
||||
playVoice(deviceId, voiceMessage);
|
||||
|
||||
sendNotifyMessage(1L,
|
||||
CleanNotificationConstants.TemplateCode.NEXT_TASK,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.nextTaskParams(queueCount, orderTitle));
|
||||
} catch (Exception e) {
|
||||
log.error("[下一任务通知] 发送失败: deviceId={}", deviceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Async("ops-task-executor")
|
||||
public void sendPriorityUpgradeNotification(Long deviceId, String orderCode, Long orderId) {
|
||||
try {
|
||||
log.warn("[P0紧急通知] deviceId={}, orderCode={}", deviceId, orderCode);
|
||||
|
||||
String voiceMessage = CleanNotificationConstants.VoiceBuilder.buildPriorityUpgrade(orderCode);
|
||||
playVoiceUrgent(deviceId, voiceMessage, orderId);
|
||||
|
||||
sendNotifyMessage(1L,
|
||||
CleanNotificationConstants.TemplateCode.PRIORITY_UPGRADE,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.priorityUpgradeParams(orderCode, "P0紧急任务"));
|
||||
} catch (Exception e) {
|
||||
log.error("[P0紧急通知] 发送失败: deviceId={}", deviceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Async("ops-task-executor")
|
||||
public void sendTaskResumedNotification(Long deviceId, String areaName) {
|
||||
try {
|
||||
log.info("[任务恢复通知] deviceId={}, areaName={}", deviceId, areaName);
|
||||
|
||||
String voiceMessage = CleanNotificationConstants.VoiceBuilder.buildTaskResumed(areaName);
|
||||
playVoice(deviceId, voiceMessage);
|
||||
|
||||
sendNotifyMessage(1L,
|
||||
CleanNotificationConstants.TemplateCode.TASK_RESUMED,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.taskResumedParams(areaName));
|
||||
} catch (Exception e) {
|
||||
log.error("[任务恢复通知] 发送失败: deviceId={}", deviceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendOrderCompletedNotification(Long orderId, Long deviceId) {
|
||||
try {
|
||||
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("[工单完成通知] orderId={}, orderCode={}, areaId={}, deviceId={}",
|
||||
orderId, order.getOrderCode(), order.getAreaId(), deviceId);
|
||||
|
||||
if (deviceId != null) {
|
||||
playVoice(deviceId, CleanNotificationConstants.VoiceTemplate.ORDER_COMPLETED, orderId);
|
||||
}
|
||||
|
||||
sendNotifyMessage(1L,
|
||||
CleanNotificationConstants.TemplateCode.ORDER_COMPLETED,
|
||||
CleanNotificationConstants.NotifyParamsBuilder.orderCompletedParams(
|
||||
order.getOrderCode(),
|
||||
CleanNotificationConstants.VoiceBuilder.getAreaName(order.getLocation()),
|
||||
order.getTitle()));
|
||||
} catch (Exception e) {
|
||||
log.error("[工单完成通知] 发送失败: orderId={}, deviceId={}", orderId, deviceId, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Async("ops-task-executor")
|
||||
public void playVoiceForNewOrder(Long deviceId) {
|
||||
playVoice(deviceId, CleanNotificationConstants.VoiceTemplate.NEW_ORDER_SHORT);
|
||||
}
|
||||
|
||||
private void playVoice(Long deviceId, String message) {
|
||||
playVoice(deviceId, message, null);
|
||||
}
|
||||
|
||||
private void playVoice(Long deviceId, String message, Long orderId) {
|
||||
try {
|
||||
voiceBroadcastService.broadcastInOrder(deviceId, message, orderId);
|
||||
log.debug("[语音播报] 调用成功: deviceId={}, message={}", deviceId, message);
|
||||
} catch (Exception e) {
|
||||
log.error("[语音播报] 调用失败: deviceId={}, message={}", deviceId, message, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void playVoiceUrgent(Long deviceId, String message, Long orderId) {
|
||||
try {
|
||||
voiceBroadcastService.broadcastUrgent(deviceId, message, orderId);
|
||||
log.debug("[语音播报-紧急] 调用成功: deviceId={}, message={}", deviceId, message);
|
||||
} catch (Exception e) {
|
||||
log.error("[语音播报-紧急] 调用失败: deviceId={}, message={}", deviceId, message, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendNotifyMessage(Long userId, String templateCode, Map<String, Object> templateParams) {
|
||||
try {
|
||||
NotifySendSingleToUserReqDTO reqDTO = new NotifySendSingleToUserReqDTO();
|
||||
reqDTO.setUserId(userId);
|
||||
reqDTO.setTemplateCode(templateCode);
|
||||
reqDTO.setTemplateParams(templateParams);
|
||||
notifyMessageSendApi.sendSingleMessageToMember(reqDTO);
|
||||
log.debug("[站内信发送成功] userId={}, templateCode={}", userId, templateCode);
|
||||
} catch (Exception e) {
|
||||
log.error("[站内信发送失败] userId={}, templateCode={}", userId, templateCode, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.viewsh.module.ops.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 手动动作类型枚举
|
||||
* <p>
|
||||
* 标识管理员手动执行的工单操作类型,用于审计日志的 actionType 字段。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum ManualActionTypeEnum {
|
||||
|
||||
MANUAL_CREATE("MANUAL_CREATE", "手动创建"),
|
||||
MANUAL_DISPATCH("MANUAL_DISPATCH", "手动派单"),
|
||||
MANUAL_UPGRADE_PRIORITY("MANUAL_UPGRADE_PRIORITY", "手动升级优先级"),
|
||||
MANUAL_CANCEL("MANUAL_CANCEL", "手动取消"),
|
||||
MANUAL_COMPLETE("MANUAL_COMPLETE", "手动完单"),
|
||||
;
|
||||
|
||||
private final String type;
|
||||
private final String name;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.viewsh.module.ops.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 工单动作来源枚举
|
||||
* <p>
|
||||
* 标识手动动作的发起来源渠道。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum OrderActionSourceEnum {
|
||||
|
||||
ADMIN_CONSOLE("ADMIN_CONSOLE", "管理后台"),
|
||||
OPEN_API("OPEN_API", "开放接口"),
|
||||
SYSTEM_TASK("SYSTEM_TASK", "系统定时任务"),
|
||||
;
|
||||
|
||||
private final String source;
|
||||
private final String name;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package com.viewsh.module.ops.enums;
|
||||
|
||||
/**
|
||||
* 工单审计 payload 键名常量
|
||||
* <p>
|
||||
* 统一 ops_business_event_log.event_payload 中的稳定键名,
|
||||
* 确保保洁/安保/工程/客服各条线写入一致的 payload 结构。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public final class OrderAuditPayloadKeys {
|
||||
|
||||
private OrderAuditPayloadKeys() {}
|
||||
|
||||
// ===== 通用字段 =====
|
||||
public static final String BUSINESS_TYPE = "businessType";
|
||||
public static final String ACTION_TYPE = "actionType";
|
||||
public static final String MANUAL = "manual";
|
||||
public static final String SOURCE = "source";
|
||||
public static final String REASON = "reason";
|
||||
|
||||
// ===== 派单相关 =====
|
||||
public static final String ASSIGNEE_ID = "assigneeId";
|
||||
public static final String ASSIGNEE_NAME = "assigneeName";
|
||||
|
||||
// ===== 优先级相关 =====
|
||||
public static final String OLD_PRIORITY = "oldPriority";
|
||||
public static final String NEW_PRIORITY = "newPriority";
|
||||
|
||||
// ===== 完单相关 =====
|
||||
public static final String RESULT = "result";
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.viewsh.module.ops.enums;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 工单事件类型枚举
|
||||
* <p>
|
||||
* 标准化 ops_order_event.event_type 字段的值,
|
||||
* 禁止在 controller/service/listener 中散落使用裸字符串。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum OrderEventTypeEnum {
|
||||
|
||||
CREATE("CREATE", "工单创建"),
|
||||
ASSIGN("ASSIGN", "工单分配"),
|
||||
ENQUEUE("QUEUED", "任务入队"),
|
||||
DISPATCH("DISPATCHED", "任务派发"),
|
||||
ACCEPT("ACCEPT", "接单确认"),
|
||||
CONFIRM("CONFIRMED", "工单确认"),
|
||||
ARRIVE("ARRIVE", "到岗确认"),
|
||||
PAUSE("PAUSE", "工单暂停"),
|
||||
RESUME("RESUME", "恢复作业"),
|
||||
COMPLETE("COMPLETE", "工单完成"),
|
||||
CANCEL("CANCEL", "工单取消"),
|
||||
UPGRADE_PRIORITY("UPGRADE_PRIORITY", "优先级升级"),
|
||||
;
|
||||
|
||||
private final String type;
|
||||
private final String name;
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
package com.viewsh.module.ops.core.manual;
|
||||
|
||||
import com.viewsh.module.ops.core.lifecycle.OrderLifecycleManager;
|
||||
import com.viewsh.module.ops.core.lifecycle.model.OrderTransitionRequest;
|
||||
import com.viewsh.module.ops.core.lifecycle.model.OrderTransitionResult;
|
||||
import com.viewsh.module.ops.core.manual.audit.OrderAuditService;
|
||||
import com.viewsh.module.ops.core.manual.model.*;
|
||||
import com.viewsh.module.ops.core.manual.strategy.OrderBusinessStrategy;
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
|
||||
import com.viewsh.module.ops.enums.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统一手动动作门面
|
||||
* <p>
|
||||
* 所有手动动作(派单/升级/取消/完单)的统一执行骨架:
|
||||
* <ol>
|
||||
* <li>查询工单并校验业务类型</li>
|
||||
* <li>校验基础状态</li>
|
||||
* <li>调用条线策略做前置校验</li>
|
||||
* <li>执行状态变更(通过 OrderLifecycleManager)</li>
|
||||
* <li>写统一审计记录</li>
|
||||
* <li>调用条线策略处理后置逻辑</li>
|
||||
* </ol>
|
||||
* <p>
|
||||
* 注意:手动创建(create)因各条线差异过大,不在此门面中统一,
|
||||
* 由各条线的专用服务直接实现。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ManualOrderActionFacade {
|
||||
|
||||
@Resource
|
||||
private OpsOrderMapper opsOrderMapper;
|
||||
|
||||
@Resource
|
||||
private OrderLifecycleManager orderLifecycleManager;
|
||||
|
||||
@Resource
|
||||
private OrderAuditService orderAuditService;
|
||||
|
||||
@Resource
|
||||
private List<OrderBusinessStrategy> strategies;
|
||||
|
||||
// ==================== 手动派单 ====================
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void dispatch(DispatchOrderCommand cmd) {
|
||||
// 1. 查单 + 校验
|
||||
OpsOrderDO order = getOrderAndValidateType(cmd.getOrderId(), cmd.getBusinessType());
|
||||
WorkOrderStatusEnum status = WorkOrderStatusEnum.valueOf(order.getStatus());
|
||||
if (status != WorkOrderStatusEnum.PENDING) {
|
||||
throw new IllegalStateException("当前工单状态不允许手动派单,仅 PENDING 状态可操作");
|
||||
}
|
||||
|
||||
// 2. 条线前置校验
|
||||
OrderBusinessStrategy strategy = resolveStrategy(cmd.getBusinessType());
|
||||
strategy.validateDispatch(cmd, order);
|
||||
|
||||
// 3. 状态变更
|
||||
OrderTransitionRequest request = OrderTransitionRequest.builder()
|
||||
.orderId(cmd.getOrderId())
|
||||
.targetStatus(WorkOrderStatusEnum.DISPATCHED)
|
||||
.assigneeId(cmd.getAssigneeId())
|
||||
.assigneeName(cmd.getAssigneeName())
|
||||
.operatorType(cmd.getOperator().getOperatorType())
|
||||
.operatorId(cmd.getOperator().getOperatorId())
|
||||
.reason(cmd.getReason() != null ? cmd.getReason() : "管理员手动派单")
|
||||
.build();
|
||||
OrderTransitionResult result = orderLifecycleManager.transition(request);
|
||||
if (!result.isSuccess()) {
|
||||
throw new IllegalStateException("手动派单失败: " + result.getMessage());
|
||||
}
|
||||
|
||||
// 4. 更新主表执行人
|
||||
order.setAssigneeId(cmd.getAssigneeId());
|
||||
order.setAssigneeName(cmd.getAssigneeName());
|
||||
opsOrderMapper.updateById(order);
|
||||
|
||||
// 5. 审计
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put(OrderAuditPayloadKeys.ASSIGNEE_ID, cmd.getAssigneeId());
|
||||
payload.put(OrderAuditPayloadKeys.ASSIGNEE_NAME, cmd.getAssigneeName());
|
||||
if (cmd.getReason() != null) {
|
||||
payload.put(OrderAuditPayloadKeys.REASON, cmd.getReason());
|
||||
}
|
||||
String message = String.format("管理员手动派单给 %s",
|
||||
cmd.getAssigneeName() != null ? cmd.getAssigneeName() : cmd.getAssigneeId());
|
||||
orderAuditService.record(order, ManualActionTypeEnum.MANUAL_DISPATCH,
|
||||
cmd.getOperator(), message, payload);
|
||||
|
||||
// 6. 条线后置
|
||||
strategy.afterDispatch(cmd, order);
|
||||
|
||||
log.info("[ManualOrderActionFacade] 手动派单完成: orderId={}, assigneeId={}", cmd.getOrderId(), cmd.getAssigneeId());
|
||||
}
|
||||
|
||||
// ==================== 手动升级优先级 ====================
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void upgradePriority(UpgradePriorityCommand cmd) {
|
||||
// 1. 查单 + 校验
|
||||
OpsOrderDO order = getOrderAndValidateType(cmd.getOrderId(), cmd.getBusinessType());
|
||||
WorkOrderStatusEnum status = WorkOrderStatusEnum.valueOf(order.getStatus());
|
||||
if (status == WorkOrderStatusEnum.COMPLETED || status == WorkOrderStatusEnum.CANCELLED) {
|
||||
throw new IllegalStateException("已完成或已取消的工单不允许升级优先级");
|
||||
}
|
||||
|
||||
// 幂等
|
||||
Integer oldPriority = order.getPriority();
|
||||
cmd.setOldPriority(oldPriority);
|
||||
if (oldPriority != null && oldPriority.equals(cmd.getNewPriority())) {
|
||||
log.info("[ManualOrderActionFacade] 优先级未变化,幂等返回: orderId={}", cmd.getOrderId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 条线前置校验
|
||||
OrderBusinessStrategy strategy = resolveStrategy(cmd.getBusinessType());
|
||||
strategy.validateUpgradePriority(cmd, order);
|
||||
|
||||
// 3. 更新优先级
|
||||
order.setPriority(cmd.getNewPriority());
|
||||
opsOrderMapper.updateById(order);
|
||||
|
||||
// 4. 审计
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put(OrderAuditPayloadKeys.OLD_PRIORITY, oldPriority);
|
||||
payload.put(OrderAuditPayloadKeys.NEW_PRIORITY, cmd.getNewPriority());
|
||||
if (cmd.getReason() != null) {
|
||||
payload.put(OrderAuditPayloadKeys.REASON, cmd.getReason());
|
||||
}
|
||||
String message = String.format("升级优先级 P%d → P%d",
|
||||
oldPriority != null ? oldPriority : 2, cmd.getNewPriority());
|
||||
orderAuditService.record(order, ManualActionTypeEnum.MANUAL_UPGRADE_PRIORITY,
|
||||
cmd.getOperator(), message, payload);
|
||||
|
||||
// 5. 条线后置
|
||||
strategy.afterUpgradePriority(cmd, order);
|
||||
|
||||
log.info("[ManualOrderActionFacade] 升级优先级完成: orderId={}, {} → {}",
|
||||
cmd.getOrderId(), oldPriority, cmd.getNewPriority());
|
||||
}
|
||||
|
||||
// ==================== 手动取消 ====================
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void cancel(CancelOrderCommand cmd) {
|
||||
// 1. 查单 + 校验
|
||||
OpsOrderDO order = getOrderAndValidateType(cmd.getOrderId(), cmd.getBusinessType());
|
||||
WorkOrderStatusEnum status = WorkOrderStatusEnum.valueOf(order.getStatus());
|
||||
|
||||
// 幂等
|
||||
if (status == WorkOrderStatusEnum.CANCELLED) {
|
||||
log.info("[ManualOrderActionFacade] 工单已取消,幂等返回: orderId={}", cmd.getOrderId());
|
||||
return;
|
||||
}
|
||||
if (status == WorkOrderStatusEnum.COMPLETED) {
|
||||
throw new IllegalStateException("已完成的工单不能取消");
|
||||
}
|
||||
|
||||
// 2. 条线前置校验
|
||||
OrderBusinessStrategy strategy = resolveStrategy(cmd.getBusinessType());
|
||||
strategy.validateCancel(cmd, order);
|
||||
|
||||
// 3. 状态变更
|
||||
orderLifecycleManager.cancelOrder(cmd.getOrderId(),
|
||||
cmd.getOperator().getOperatorId(),
|
||||
cmd.getOperator().getOperatorType(),
|
||||
cmd.getReason());
|
||||
|
||||
// 4. 审计
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
payload.put(OrderAuditPayloadKeys.REASON, cmd.getReason());
|
||||
orderAuditService.record(order, ManualActionTypeEnum.MANUAL_CANCEL,
|
||||
cmd.getOperator(), "管理员手动取消: " + cmd.getReason(), payload);
|
||||
|
||||
// 5. 条线后置
|
||||
strategy.afterCancel(cmd, order);
|
||||
|
||||
log.info("[ManualOrderActionFacade] 手动取消完成: orderId={}", cmd.getOrderId());
|
||||
}
|
||||
|
||||
// ==================== 手动完单 ====================
|
||||
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void complete(CompleteOrderCommand cmd) {
|
||||
// 1. 查单 + 校验
|
||||
OpsOrderDO order = getOrderAndValidateType(cmd.getOrderId(), cmd.getBusinessType());
|
||||
WorkOrderStatusEnum status = WorkOrderStatusEnum.valueOf(order.getStatus());
|
||||
|
||||
// 幂等
|
||||
if (status == WorkOrderStatusEnum.COMPLETED) {
|
||||
log.info("[ManualOrderActionFacade] 工单已完成,幂等返回: orderId={}", cmd.getOrderId());
|
||||
return;
|
||||
}
|
||||
if (status == WorkOrderStatusEnum.CANCELLED) {
|
||||
throw new IllegalStateException("已取消的工单不能完成");
|
||||
}
|
||||
|
||||
// 2. 条线前置校验
|
||||
OrderBusinessStrategy strategy = resolveStrategy(cmd.getBusinessType());
|
||||
strategy.validateComplete(cmd, order);
|
||||
|
||||
// 3. 状态变更
|
||||
String remark = cmd.getReason() != null ? cmd.getReason() : "管理员手动完成";
|
||||
orderLifecycleManager.completeOrder(cmd.getOrderId(),
|
||||
cmd.getOperator().getOperatorId(),
|
||||
cmd.getOperator().getOperatorType(),
|
||||
remark);
|
||||
|
||||
// 4. 审计
|
||||
Map<String, Object> payload = new HashMap<>();
|
||||
if (cmd.getReason() != null) {
|
||||
payload.put(OrderAuditPayloadKeys.REASON, cmd.getReason());
|
||||
}
|
||||
if (cmd.getResult() != null) {
|
||||
payload.put(OrderAuditPayloadKeys.RESULT, cmd.getResult());
|
||||
}
|
||||
orderAuditService.record(order, ManualActionTypeEnum.MANUAL_COMPLETE,
|
||||
cmd.getOperator(), remark, payload);
|
||||
|
||||
// 5. 条线后置
|
||||
strategy.afterComplete(cmd, order);
|
||||
|
||||
log.info("[ManualOrderActionFacade] 手动完单完成: orderId={}", cmd.getOrderId());
|
||||
}
|
||||
|
||||
// ==================== 内部方法 ====================
|
||||
|
||||
private OpsOrderDO getOrderAndValidateType(Long orderId, String expectedType) {
|
||||
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
throw new IllegalArgumentException("工单不存在: orderId=" + orderId);
|
||||
}
|
||||
if (!expectedType.equals(order.getOrderType())) {
|
||||
throw new IllegalStateException(
|
||||
String.format("工单类型不匹配: 期望 %s,实际 %s", expectedType, order.getOrderType()));
|
||||
}
|
||||
return order;
|
||||
}
|
||||
|
||||
private OrderBusinessStrategy resolveStrategy(String businessType) {
|
||||
return strategies.stream()
|
||||
.filter(s -> s.supports(businessType))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new IllegalStateException("未找到业务条线策略: " + businessType));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.viewsh.module.ops.core.manual.audit;
|
||||
|
||||
import com.viewsh.module.ops.core.manual.model.OperatorContext;
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
import com.viewsh.module.ops.enums.ManualActionTypeEnum;
|
||||
import com.viewsh.module.ops.enums.OrderAuditPayloadKeys;
|
||||
import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain;
|
||||
import com.viewsh.module.ops.infrastructure.log.enumeration.LogModule;
|
||||
import com.viewsh.module.ops.infrastructure.log.enumeration.LogType;
|
||||
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord;
|
||||
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
|
||||
import com.viewsh.module.ops.service.event.OpsOrderEventService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统一手动动作审计服务
|
||||
* <p>
|
||||
* 所有手动动作通过此服务落库,确保 ops_order_event(时间轴)和
|
||||
* ops_business_event_log(审计检索)双写一致。
|
||||
* <p>
|
||||
* personId 永远表示真实操作者,被操作目标放入 payload。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class OrderAuditService {
|
||||
|
||||
@Resource
|
||||
private OpsOrderEventService opsOrderEventService;
|
||||
|
||||
@Resource
|
||||
private EventLogRecorder eventLogRecorder;
|
||||
|
||||
/**
|
||||
* 记录手动动作审计(双写:时间轴 + 业务日志)
|
||||
*
|
||||
* @param order 工单
|
||||
* @param actionType 手动动作类型
|
||||
* @param operator 操作人上下文
|
||||
* @param message 可读消息
|
||||
* @param payload 审计扩展数据
|
||||
*/
|
||||
public void record(OpsOrderDO order, ManualActionTypeEnum actionType,
|
||||
OperatorContext operator, String message,
|
||||
Map<String, Object> payload) {
|
||||
Long orderId = order.getId();
|
||||
|
||||
// 1. 时间轴事件(ops_order_event)
|
||||
String eventType = mapToEventType(actionType);
|
||||
try {
|
||||
opsOrderEventService.recordEvent(
|
||||
orderId,
|
||||
order.getStatus(),
|
||||
order.getStatus(),
|
||||
eventType,
|
||||
operator.getOperatorType().getType(),
|
||||
operator.getOperatorId(),
|
||||
message
|
||||
);
|
||||
} catch (Exception e) {
|
||||
log.warn("[OrderAuditService] 记录时间轴事件失败: orderId={}, actionType={}", orderId, actionType, e);
|
||||
}
|
||||
|
||||
// 2. 业务审计日志(ops_business_event_log)
|
||||
try {
|
||||
Map<String, Object> auditPayload = new HashMap<>();
|
||||
auditPayload.put(OrderAuditPayloadKeys.BUSINESS_TYPE, order.getOrderType());
|
||||
auditPayload.put(OrderAuditPayloadKeys.ACTION_TYPE, actionType.getType());
|
||||
auditPayload.put(OrderAuditPayloadKeys.MANUAL, true);
|
||||
auditPayload.put(OrderAuditPayloadKeys.SOURCE, operator.getSource().getSource());
|
||||
if (payload != null) {
|
||||
auditPayload.putAll(payload);
|
||||
}
|
||||
|
||||
String module = LogModule.fromOrderType(order.getOrderType());
|
||||
eventLogRecorder.record(EventLogRecord.builder()
|
||||
.module(module)
|
||||
.domain(EventDomain.AUDIT)
|
||||
.eventType(mapToLogType(actionType).getCode())
|
||||
.message(message)
|
||||
.targetId(orderId)
|
||||
.targetType("order")
|
||||
.personId(operator.getOperatorId())
|
||||
.payload(auditPayload)
|
||||
.build());
|
||||
} catch (Exception e) {
|
||||
log.warn("[OrderAuditService] 记录审计日志失败: orderId={}, actionType={}", orderId, actionType, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ManualActionTypeEnum → ops_order_event eventType
|
||||
*/
|
||||
private String mapToEventType(ManualActionTypeEnum actionType) {
|
||||
return switch (actionType) {
|
||||
case MANUAL_CREATE -> "CREATE";
|
||||
case MANUAL_DISPATCH -> "DISPATCH";
|
||||
case MANUAL_UPGRADE_PRIORITY -> "UPGRADE_PRIORITY";
|
||||
case MANUAL_CANCEL -> "CANCEL";
|
||||
case MANUAL_COMPLETE -> "COMPLETE";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* ManualActionTypeEnum → LogType
|
||||
*/
|
||||
private LogType mapToLogType(ManualActionTypeEnum actionType) {
|
||||
return switch (actionType) {
|
||||
case MANUAL_CREATE -> LogType.ORDER_CREATED;
|
||||
case MANUAL_DISPATCH -> LogType.ORDER_DISPATCHED;
|
||||
case MANUAL_UPGRADE_PRIORITY -> LogType.PRIORITY_UPGRADE;
|
||||
case MANUAL_CANCEL -> LogType.ORDER_CANCELLED;
|
||||
case MANUAL_COMPLETE -> LogType.ORDER_COMPLETED;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.viewsh.module.ops.core.manual.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 手动取消工单命令
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CancelOrderCommand {
|
||||
|
||||
private String businessType;
|
||||
private Long orderId;
|
||||
private OperatorContext operator;
|
||||
private String reason;
|
||||
private Map<String, Object> payload;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.viewsh.module.ops.core.manual.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 手动完单命令
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CompleteOrderCommand {
|
||||
|
||||
private String businessType;
|
||||
private Long orderId;
|
||||
private OperatorContext operator;
|
||||
private String reason;
|
||||
private String result;
|
||||
private Map<String, Object> payload;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.viewsh.module.ops.core.manual.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 手动派单命令
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class DispatchOrderCommand {
|
||||
|
||||
private String businessType;
|
||||
private Long orderId;
|
||||
private OperatorContext operator;
|
||||
private Long assigneeId;
|
||||
private String assigneeName;
|
||||
private String assigneePhone;
|
||||
private String reason;
|
||||
private Map<String, Object> payload;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.viewsh.module.ops.core.manual.model;
|
||||
|
||||
import com.viewsh.module.ops.enums.OperatorTypeEnum;
|
||||
import com.viewsh.module.ops.enums.OrderActionSourceEnum;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 统一操作人上下文
|
||||
* <p>
|
||||
* 所有手动动作命令必须携带此上下文,确保审计日志和时间轴事件
|
||||
* 能稳定识别真实操作者。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class OperatorContext {
|
||||
|
||||
/** 操作人ID */
|
||||
private Long operatorId;
|
||||
|
||||
/** 操作人姓名(冗余,可由服务层填充) */
|
||||
private String operatorName;
|
||||
|
||||
/** 操作人角色(管理员、安保人员、系统等角色语义) */
|
||||
private String operatorRole;
|
||||
|
||||
/** 操作人类型(兼容状态机和事件枚举) */
|
||||
private OperatorTypeEnum operatorType;
|
||||
|
||||
/** 动作来源 */
|
||||
@Builder.Default
|
||||
private OrderActionSourceEnum source = OrderActionSourceEnum.ADMIN_CONSOLE;
|
||||
|
||||
/**
|
||||
* 便捷工厂:管理后台操作
|
||||
*/
|
||||
public static OperatorContext ofAdmin(Long operatorId) {
|
||||
return OperatorContext.builder()
|
||||
.operatorId(operatorId)
|
||||
.operatorType(OperatorTypeEnum.ADMIN)
|
||||
.source(OrderActionSourceEnum.ADMIN_CONSOLE)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷工厂:管理后台操作(带姓名)
|
||||
*/
|
||||
public static OperatorContext ofAdmin(Long operatorId, String operatorName) {
|
||||
return OperatorContext.builder()
|
||||
.operatorId(operatorId)
|
||||
.operatorName(operatorName)
|
||||
.operatorType(OperatorTypeEnum.ADMIN)
|
||||
.source(OrderActionSourceEnum.ADMIN_CONSOLE)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.viewsh.module.ops.core.manual.model;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 手动升级优先级命令
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class UpgradePriorityCommand {
|
||||
|
||||
private String businessType;
|
||||
private Long orderId;
|
||||
private OperatorContext operator;
|
||||
private Integer oldPriority;
|
||||
private Integer newPriority;
|
||||
private String reason;
|
||||
private Map<String, Object> payload;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.viewsh.module.ops.core.manual.strategy;
|
||||
|
||||
import com.viewsh.module.ops.core.manual.model.*;
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
|
||||
/**
|
||||
* 工单业务条线策略接口
|
||||
* <p>
|
||||
* 每个业务条线(保洁、安保、工程、客服)实现此接口,
|
||||
* 负责条线特有的前置校验和后置副作用。
|
||||
* <p>
|
||||
* 默认方法为空实现,条线按需覆写。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
public interface OrderBusinessStrategy {
|
||||
|
||||
/**
|
||||
* 是否支持指定业务类型
|
||||
*/
|
||||
boolean supports(String businessType);
|
||||
|
||||
// ==================== 前置校验 ====================
|
||||
|
||||
default void validateDispatch(DispatchOrderCommand cmd, OpsOrderDO order) {}
|
||||
|
||||
default void validateUpgradePriority(UpgradePriorityCommand cmd, OpsOrderDO order) {}
|
||||
|
||||
default void validateCancel(CancelOrderCommand cmd, OpsOrderDO order) {}
|
||||
|
||||
default void validateComplete(CompleteOrderCommand cmd, OpsOrderDO order) {}
|
||||
|
||||
// ==================== 后置处理 ====================
|
||||
|
||||
default void afterDispatch(DispatchOrderCommand cmd, OpsOrderDO order) {}
|
||||
|
||||
default void afterUpgradePriority(UpgradePriorityCommand cmd, OpsOrderDO order) {}
|
||||
|
||||
default void afterCancel(CancelOrderCommand cmd, OpsOrderDO order) {}
|
||||
|
||||
default void afterComplete(CompleteOrderCommand cmd, OpsOrderDO order) {}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.viewsh.module.ops.controller.admin.security.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 安保工单手动取消请求
|
||||
*/
|
||||
@Schema(description = "安保工单手动取消请求")
|
||||
@Data
|
||||
public class SecurityOrderCancelReqVO {
|
||||
|
||||
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "工单ID不能为空")
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "取消原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "误报,无需处置")
|
||||
@NotBlank(message = "取消原因不能为空")
|
||||
private String reason;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.viewsh.module.ops.controller.admin.security.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 安保工单手动派单请求 VO
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Schema(description = "安保工单手动派单请求")
|
||||
@Data
|
||||
public class SecurityOrderDispatchReqVO {
|
||||
|
||||
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
|
||||
@NotNull(message = "工单ID不能为空")
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "指定安保人员ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "144")
|
||||
@NotNull(message = "安保人员ID不能为空")
|
||||
private Long assigneeId;
|
||||
|
||||
@Schema(description = "派单备注", example = "紧急情况,指定该人员处理")
|
||||
private String remark;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.viewsh.module.ops.controller.admin.security.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.Max;
|
||||
import jakarta.validation.constraints.Min;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 安保工单优先级升级请求 VO
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Schema(description = "安保工单优先级升级请求")
|
||||
@Data
|
||||
public class SecurityOrderUpgradePriorityReqVO {
|
||||
|
||||
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
|
||||
@NotNull(message = "工单ID不能为空")
|
||||
private Long orderId;
|
||||
|
||||
@Schema(description = "目标优先级(0=P0, 1=P1, 2=P2)", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
|
||||
@NotNull(message = "目标优先级不能为空")
|
||||
@Min(value = 0, message = "优先级最小为0(P0)")
|
||||
@Max(value = 2, message = "优先级最大为2(P2)")
|
||||
private Integer priority;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.viewsh.module.ops.security.service.manual;
|
||||
|
||||
import com.viewsh.module.ops.api.queue.OrderQueueService;
|
||||
import com.viewsh.module.ops.core.manual.model.*;
|
||||
import com.viewsh.module.ops.core.manual.strategy.OrderBusinessStrategy;
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
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 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>
|
||||
* 处理安保特有的前置校验和后置副作用。
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class SecurityOrderBusinessStrategy implements OrderBusinessStrategy {
|
||||
|
||||
@Resource
|
||||
private OpsAreaSecurityUserMapper areaSecurityUserMapper;
|
||||
|
||||
@Resource
|
||||
private OrderQueueService orderQueueService;
|
||||
|
||||
@Override
|
||||
public boolean supports(String businessType) {
|
||||
return WorkOrderTypeEnum.SECURITY.getType().equals(businessType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateDispatch(DispatchOrderCommand cmd, OpsOrderDO order) {
|
||||
// 校验目标安保人员:必须绑定到工单所属区域且已启用
|
||||
OpsAreaSecurityUserDO binding = areaSecurityUserMapper.selectByAreaIdAndUserId(
|
||||
order.getAreaId(), cmd.getAssigneeId());
|
||||
if (binding == null || !Boolean.TRUE.equals(binding.getEnabled())) {
|
||||
throw exception(SECURITY_ASSIGNEE_NOT_BOUND_TO_AREA);
|
||||
}
|
||||
// 使用绑定记录中的冗余姓名
|
||||
if (cmd.getAssigneeName() == null && binding.getUserName() != null) {
|
||||
cmd.setAssigneeName(binding.getUserName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterUpgradePriority(UpgradePriorityCommand cmd, OpsOrderDO order) {
|
||||
// 如果工单在队列中,触发队列分数重算
|
||||
if (WorkOrderStatusEnum.QUEUED.getStatus().equals(order.getStatus()) && order.getAssigneeId() != null) {
|
||||
orderQueueService.rebuildWaitingTasksByUserId(order.getAssigneeId(), order.getAreaId());
|
||||
log.info("[SecurityStrategy] 升级优先级后重算队列: orderId={}, assigneeId={}",
|
||||
cmd.getOrderId(), order.getAssigneeId());
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user