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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user