diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanWorkOrderService.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanWorkOrderService.java index 68c9b00..c0a5a10 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanWorkOrderService.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanWorkOrderService.java @@ -1,6 +1,9 @@ package com.viewsh.module.ops.environment.service.cleanorder; import com.viewsh.module.ops.api.clean.OrderTimelineRespDTO; +import com.viewsh.module.ops.environment.service.cleanorder.dto.CleanManualCancelReqDTO; +import com.viewsh.module.ops.environment.service.cleanorder.dto.CleanManualCreateReqDTO; +import com.viewsh.module.ops.environment.service.cleanorder.dto.CleanManualDispatchReqDTO; import com.viewsh.module.ops.environment.service.cleanorder.dto.ManualCompleteOrderReqDTO; import com.viewsh.module.ops.environment.service.cleanorder.dto.UpgradePriorityReqDTO; @@ -47,4 +50,32 @@ public interface CleanWorkOrderService { * @param req 升级优先级请求 */ void upgradePriority(UpgradePriorityReqDTO req); + + /** + * 手动创建保洁工单 + *

+ * 管理员手动创建保洁工单,补齐主表 + 扩展表 + 创建事件 + 审计日志 + * + * @param req 手动创建请求 + * @return 工单ID + */ + Long manualCreateOrder(CleanManualCreateReqDTO req); + + /** + * 手动派单 + *

+ * 管理员指定设备/人员,绕过自动调度直接派单 + * + * @param req 手动派单请求 + */ + void manualDispatch(CleanManualDispatchReqDTO req); + + /** + * 手动取消工单 + *

+ * 管理员手动取消工单,走完整生命周期链(停止播报、恢复状态、自动派发下一单) + * + * @param req 手动取消请求 + */ + void manualCancelOrder(CleanManualCancelReqDTO req); } diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanWorkOrderServiceImpl.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanWorkOrderServiceImpl.java index af3cf68..fd8e3d7 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanWorkOrderServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/CleanWorkOrderServiceImpl.java @@ -2,16 +2,30 @@ package com.viewsh.module.ops.environment.service.cleanorder; import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX; import com.viewsh.module.ops.api.clean.OrderTimelineRespDTO; +import com.viewsh.module.ops.api.queue.OrderQueueService; +import com.viewsh.module.ops.core.event.OrderCreatedEvent; +import com.viewsh.module.ops.core.event.OrderEventPublisher; import com.viewsh.module.ops.core.lifecycle.OrderLifecycleManager; +import com.viewsh.module.ops.core.manual.ManualOrderActionFacade; +import com.viewsh.module.ops.core.manual.audit.OrderAuditService; +import com.viewsh.module.ops.core.manual.model.*; +import com.viewsh.module.ops.dal.dataobject.area.OpsBusAreaDO; import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO; import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderEventDO; +import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper; import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderEventMapper; import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper; -import com.viewsh.module.ops.enums.OperatorTypeEnum; -import com.viewsh.module.ops.enums.WorkOrderStatusEnum; -import com.viewsh.module.ops.environment.service.cleanorder.dto.ManualCompleteOrderReqDTO; -import com.viewsh.module.ops.environment.service.cleanorder.dto.UpgradePriorityReqDTO; +import com.viewsh.module.ops.enums.*; +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.service.cleanorder.dto.*; +import com.viewsh.module.ops.infrastructure.area.AreaPathBuilder; +import com.viewsh.module.ops.infrastructure.code.OrderCodeGenerator; +import com.viewsh.module.ops.infrastructure.id.OrderIdGenerator; +import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder; import com.viewsh.module.ops.service.event.OpsOrderEventService; +import com.viewsh.module.system.api.user.AdminUserApi; +import com.viewsh.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -41,10 +55,33 @@ public class CleanWorkOrderServiceImpl implements CleanWorkOrderService { private OpsOrderEventMapper opsOrderEventMapper; @Resource - private OpsOrderEventService opsOrderEventService; + private AdminUserApi adminUserApi; @Resource - private OrderLifecycleManager orderLifecycleManager; + private OrderIdGenerator orderIdGenerator; + + @Resource + private OrderCodeGenerator orderCodeGenerator; + + @Resource + private OrderEventPublisher orderEventPublisher; + + @Resource + private OpsBusAreaMapper opsBusAreaMapper; + + @Resource + private AreaPathBuilder areaPathBuilder; + + @Resource + private OpsOrderCleanExtMapper cleanExtMapper; + + @Resource + private ManualOrderActionFacade manualOrderActionFacade; + + @Resource + private OrderAuditService orderAuditService; + + private static final String BUSINESS_TYPE = WorkOrderTypeEnum.CLEAN.getType(); /** * 事件类型名称映射 @@ -124,75 +161,24 @@ public class CleanWorkOrderServiceImpl implements CleanWorkOrderService { } @Override - @Transactional(rollbackFor = Exception.class) public void manualCompleteOrder(ManualCompleteOrderReqDTO req) { - Long orderId = req.getOrderId(); - - // 1. 查询工单 - OpsOrderDO order = opsOrderMapper.selectById(orderId); - if (order == null) { - throw new IllegalArgumentException("工单不存在: orderId=" + orderId); - } - - // 2. 校验:已完成的工单幂等返回 - if (WorkOrderStatusEnum.COMPLETED.getStatus().equals(order.getStatus())) { - log.info("[manualCompleteOrder] 工单已处于完成状态,幂等返回: orderId={}", orderId); - return; - } - - // 3. 校验:已取消的工单不能完成 - if (WorkOrderStatusEnum.CANCELLED.getStatus().equals(order.getStatus())) { - throw new IllegalStateException("已取消的工单不能完成: orderId=" + orderId); - } - - // 4. 通过 OrderLifecycleManager 走完整责任链(状态校验→队列同步→事件发布) - String remark = req.getRemark() != null ? req.getRemark() : "管理员手动完成"; - orderLifecycleManager.completeOrder(orderId, req.getOperatorId(), OperatorTypeEnum.ADMIN, remark); - - log.info("[manualCompleteOrder] 手动完成工单成功: orderId={}, operatorId={}, remark={}", - orderId, req.getOperatorId(), remark); + manualOrderActionFacade.complete(CompleteOrderCommand.builder() + .businessType(BUSINESS_TYPE) + .orderId(req.getOrderId()) + .operator(OperatorContext.ofAdmin(req.getOperatorId(), resolveUserName(req.getOperatorId()))) + .reason(req.getRemark()) + .build()); } @Override - @Transactional(rollbackFor = Exception.class) public void upgradePriority(UpgradePriorityReqDTO req) { - try { - Long orderId = req.getOrderId(); - - // 1. 查询工单 - OpsOrderDO order = opsOrderMapper.selectById(orderId); - if (order == null) { - log.warn("[upgradePriority] 工单不存在: orderId={}", orderId); - return; - } - - Integer oldPriority = order.getPriority(); - - // 2. 更新工单优先级为 P0 - OpsOrderDO updateDO = new OpsOrderDO(); - updateDO.setId(orderId); - updateDO.setPriority(0); // P0 - opsOrderMapper.updateById(updateDO); - - // 3. 记录事件 - opsOrderEventService.recordEvent( - orderId, - order.getStatus(), - order.getStatus(), - "UPGRADE_PRIORITY", - "ADMIN", - null, // 管理员操作 - String.format("优先级从 P%d 升级为 P0: %s", - oldPriority != null ? oldPriority : 1, req.getReason()) - ); - - log.info("[upgradePriority] 升级工单优先级成功: orderId={}, oldPriority={}, reason={}", - orderId, oldPriority, req.getReason()); - - } catch (Exception e) { - log.error("[upgradePriority] 升级工单优先级失败: orderId={}", req.getOrderId(), e); - throw e; - } + manualOrderActionFacade.upgradePriority(UpgradePriorityCommand.builder() + .businessType(BUSINESS_TYPE) + .orderId(req.getOrderId()) + .operator(OperatorContext.ofAdmin(req.getOperatorId(), resolveUserName(req.getOperatorId()))) + .newPriority(req.getEffectiveNewPriority()) + .reason(req.getReason()) + .build()); } // ==================== 私有方法 ==================== @@ -208,7 +194,7 @@ public class CleanWorkOrderServiceImpl implements CleanWorkOrderService { String operator = event.getOperatorName(); if (operator == null || operator.isEmpty()) { - if ("SYSTEM".equals(event.getOperatorType())) { + if (OperatorTypeEnum.SYSTEM.getType().equals(event.getOperatorType())) { operator = "系统"; } else { operator = "操作员"; @@ -259,8 +245,8 @@ public class CleanWorkOrderServiceImpl implements CleanWorkOrderService { */ private Map buildPendingExtraInfo(OpsOrderDO order) { Map extra = new HashMap<>(); - extra.put("event_type", "CREATE"); - extra.put("operator_type", "SYSTEM"); + extra.put("event_type", OrderEventTypeEnum.CREATE.getType()); + extra.put("operator_type", OperatorTypeEnum.SYSTEM.getType()); extra.put("order_code", order.getOrderCode()); extra.put("priority", order.getPriority()); extra.put("source_type", order.getSourceType()); @@ -269,4 +255,111 @@ public class CleanWorkOrderServiceImpl implements CleanWorkOrderService { } return extra; } + + /** + * 根据用户 ID 查询姓名,查询失败返回 null(不影响主流程) + */ + private String resolveUserName(Long userId) { + if (userId == null) { + return null; + } + try { + AdminUserRespDTO user = adminUserApi.getUser(userId).getCheckedData(); + return user != null ? user.getNickname() : null; + } catch (Exception e) { + log.debug("[resolveUserName] 查询用户姓名失败: userId={}", userId); + return null; + } + } + + // ==================== 手动创建 ==================== + + @Override + @Transactional(rollbackFor = Exception.class) + public Long manualCreateOrder(CleanManualCreateReqDTO req) { + // 0. 校验区域 + OpsBusAreaDO area = opsBusAreaMapper.selectById(req.getAreaId()); + if (area == null) { + throw new IllegalArgumentException("区域不存在: areaId=" + req.getAreaId()); + } + + // 1. 生成ID和编号 + Long orderId = orderIdGenerator.generate(); + String orderCode = orderCodeGenerator.generate(BUSINESS_TYPE); + + // 2. 插入主表 + OpsOrderDO order = OpsOrderDO.builder() + .id(orderId) + .orderCode(orderCode) + .orderType(BUSINESS_TYPE) + .sourceType(SourceTypeEnum.MANUAL.getType()) + .title(req.getTitle()) + .description(req.getDescription()) + .priority(req.getPriority() != null ? req.getPriority() : PriorityEnum.P2.getPriority()) + .status(WorkOrderStatusEnum.PENDING.getStatus()) + .areaId(req.getAreaId()) + .location(areaPathBuilder.buildPath(area)) + .build(); + opsOrderMapper.insert(order); + + // 3. 插入扩展表 + OpsOrderCleanExtDO cleanExt = OpsOrderCleanExtDO.builder() + .id(orderIdGenerator.generate()) + .opsOrderId(orderId) + .isAuto(0) // 手动创建 + .expectedDuration(req.getExpectedDuration()) + .cleaningType(req.getCleaningType()) + .build(); + cleanExtMapper.insert(cleanExt); + + // 4. 发布工单创建事件(触发自动派单) + OrderCreatedEvent event = OrderCreatedEvent.builder() + .orderId(orderId) + .orderType(BUSINESS_TYPE) + .orderCode(orderCode) + .title(req.getTitle()) + .areaId(req.getAreaId()) + .priority(order.getPriority()) + .createTime(order.getCreateTime()) + .build() + .addPayload("isAuto", false); + orderEventPublisher.publishOrderCreated(event); + + // 5. 记录审计日志(ops_business_event_log) + String operatorName = resolveUserName(req.getOperatorId()); + String message = operatorName != null + ? String.format("%s 创建了保洁工单", operatorName) + : "手动创建保洁工单"; + orderAuditService.record(order, ManualActionTypeEnum.MANUAL_CREATE, + OperatorContext.ofAdmin(req.getOperatorId(), operatorName), message, null); + + log.info("[manualCreateOrder] 手动创建保洁工单成功: orderId={}, orderCode={}, operatorId={}", + orderId, orderCode, req.getOperatorId()); + return orderId; + } + + // ==================== 手动派单(委托 Facade) ==================== + + @Override + public void manualDispatch(CleanManualDispatchReqDTO req) { + manualOrderActionFacade.dispatch(DispatchOrderCommand.builder() + .businessType(BUSINESS_TYPE) + .orderId(req.getOrderId()) + .operator(OperatorContext.ofAdmin(req.getOperatorId(), resolveUserName(req.getOperatorId()))) + .assigneeId(req.getAssigneeId()) + .reason(req.getRemark()) + .build()); + } + + // ==================== 手动取消(委托 Facade) ==================== + + @Override + public void manualCancelOrder(CleanManualCancelReqDTO req) { + manualOrderActionFacade.cancel(CancelOrderCommand.builder() + .businessType(BUSINESS_TYPE) + .orderId(req.getOrderId()) + .operator(OperatorContext.ofAdmin(req.getOperatorId(), resolveUserName(req.getOperatorId()))) + .reason(req.getReason()) + .build()); + } } diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/dto/UpgradePriorityReqDTO.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/dto/UpgradePriorityReqDTO.java index e311e31..874ca30 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/dto/UpgradePriorityReqDTO.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/cleanorder/dto/UpgradePriorityReqDTO.java @@ -7,8 +7,6 @@ import lombok.Data; /** * 升级工单优先级请求 DTO - *

- * 用于将普通工单升级为 P0 紧急工单 * * @author lzh */ @@ -20,7 +18,20 @@ public class UpgradePriorityReqDTO { @NotNull(message = "工单ID不能为空") private Long orderId; + @Schema(description = "目标优先级(0=P0紧急, 1=P1重要, 2=P2普通),默认 0(P0)", example = "0") + private Integer newPriority; + @Schema(description = "升级原因", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户投诉急需处理") @NotBlank(message = "升级原因不能为空") private String reason; + + @Schema(description = "操作人ID(由 Controller 注入)", hidden = true) + private Long operatorId; + + /** + * 获取目标优先级,默认 P0 + */ + public Integer getEffectiveNewPriority() { + return newPriority != null ? newPriority : 0; + } } diff --git a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/manual/CleanOrderBusinessStrategy.java b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/manual/CleanOrderBusinessStrategy.java index 35f441f..f90e2ee 100644 --- a/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/manual/CleanOrderBusinessStrategy.java +++ b/viewsh-module-ops/viewsh-module-environment-biz/src/main/java/com/viewsh/module/ops/environment/service/manual/CleanOrderBusinessStrategy.java @@ -1,21 +1,22 @@ package com.viewsh.module.ops.environment.service.manual; +import com.viewsh.module.ops.api.badge.BadgeDeviceStatusDTO; +import com.viewsh.module.ops.api.queue.OrderQueueDTO; import com.viewsh.module.ops.api.queue.OrderQueueService; -import com.viewsh.module.ops.core.manual.model.*; +import com.viewsh.module.ops.core.manual.model.DispatchOrderCommand; +import com.viewsh.module.ops.core.manual.model.UpgradePriorityCommand; 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.PriorityEnum; import com.viewsh.module.ops.enums.WorkOrderTypeEnum; +import com.viewsh.module.ops.environment.service.badge.BadgeDeviceStatusService; +import com.viewsh.module.ops.environment.service.notification.CleanOrderNotificationService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; /** - * 保洁条线策略 - *

- * 处理保洁特有的前置校验和后置副作用。 - * - * @author lzh + * 保洁条线策略。 */ @Slf4j @Component @@ -24,18 +25,60 @@ public class CleanOrderBusinessStrategy implements OrderBusinessStrategy { @Resource private OrderQueueService orderQueueService; + @Resource + private CleanOrderNotificationService cleanOrderNotificationService; + + @Resource + private BadgeDeviceStatusService badgeDeviceStatusService; + @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()); + public void validateDispatch(DispatchOrderCommand cmd, OpsOrderDO order) { + if (cmd.getAssigneeId() == null) { + throw new IllegalArgumentException("手动派单目标设备不能为空"); + } + + BadgeDeviceStatusDTO badge = badgeDeviceStatusService.getBadgeStatus(cmd.getAssigneeId()); + if (badge == null) { + throw new IllegalStateException("目标保洁设备不存在"); + } + if (!badge.isOnline()) { + throw new IllegalStateException("目标保洁设备当前离线,不能手动派单"); + } + if (!badge.canAcceptNewOrder()) { + throw new IllegalStateException("目标保洁设备当前不可接单"); + } + if (order.getAreaId() != null && badge.getCurrentAreaId() == null) { + throw new IllegalStateException("目标保洁设备当前未绑定区域,不能手动派单"); + } + if (order.getAreaId() != null && !order.getAreaId().equals(badge.getCurrentAreaId())) { + throw new IllegalStateException("目标保洁设备不在当前工单所属区域"); } } + + @Override + public void afterUpgradePriority(UpgradePriorityCommand cmd, OpsOrderDO order) { + OrderQueueDTO queueDTO = orderQueueService.getByOpsOrderId(cmd.getOrderId()); + if (queueDTO == null) { + return; + } + + PriorityEnum newPriority = PriorityEnum.fromPriority(cmd.getNewPriority()); + String reason = cmd.getReason() != null ? cmd.getReason() : "手动升级优先级"; + + orderQueueService.adjustPriority(queueDTO.getId(), newPriority, reason); + orderQueueService.rebuildWaitingTasksByUserId(queueDTO.getUserId(), order.getAreaId()); + + if (newPriority == PriorityEnum.P0) { + cleanOrderNotificationService.sendPriorityUpgradeNotification( + queueDTO.getUserId(), order.getOrderCode(), cmd.getOrderId()); + } + + log.info("[CleanStrategy] 升级优先级后置完成: orderId={}, newPriority={}, queueId={}", + cmd.getOrderId(), newPriority, queueDTO.getId()); + } }