diff --git a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/SecurityOrderController.java b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/SecurityOrderController.java index d908323..d7888aa 100644 --- a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/SecurityOrderController.java +++ b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/admin/security/SecurityOrderController.java @@ -94,7 +94,7 @@ public class SecurityOrderController { @Operation(summary = "误报标记", description = "将安保工单标记为误报并完成") @PreAuthorize("@ss.hasPermission('ops:security-order:complete')") public CommonResult falseAlarmOrder(@Valid @RequestBody SecurityOrderIdReqVO reqVO) { - securityOrderService.falseAlarmOrder(reqVO.getOrderId(), SecurityFrameworkUtils.getLoginUserId()); + securityOrderService.falseAlarmOrder(reqVO.getOrderId()); return success(true); } diff --git a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/open/security/SecurityOrderOpenController.java b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/open/security/SecurityOrderOpenController.java index aa20670..0e88f0b 100644 --- a/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/open/security/SecurityOrderOpenController.java +++ b/viewsh-module-ops/viewsh-module-ops-server/src/main/java/com/viewsh/module/ops/controller/open/security/SecurityOrderOpenController.java @@ -99,7 +99,7 @@ public class SecurityOrderOpenController { @ApiSignature @PermitAll public CommonResult falseAlarmOrder(@Valid @RequestBody SecurityOrderIdReqVO reqVO) { - securityOrderService.falseAlarmOrder(reqVO.getOrderId(), null); + securityOrderService.falseAlarmOrder(reqVO.getOrderId()); return success(true); } diff --git a/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderService.java b/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderService.java index b77cb47..d04b45c 100644 --- a/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderService.java +++ b/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderService.java @@ -30,7 +30,14 @@ public interface SecurityOrderService { void confirmOrder(Long orderId, Long userId); /** - * 自动完单(对方系统调用,无需提交结果) + * 自动完单(由边缘系统调用,告警自动解除) + *

+ * 根据当前工单状态: + *

* * @param orderId 工单ID * @param remark 备注 @@ -45,12 +52,11 @@ public interface SecurityOrderService { void manualCompleteOrder(SecurityOrderCompleteReqDTO req); /** - * 误报标记(将工单标记为误报并完成) + * 误报标记(复用自动完单逻辑,额外标记误报) * - * @param orderId 工单ID - * @param operatorId 操作人ID(可为 null,为 null 时取已分配人员) + * @param orderId 工单ID */ - void falseAlarmOrder(Long orderId, Long operatorId); + void falseAlarmOrder(Long orderId); /** * 根据工单ID查询安保扩展信息 diff --git a/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceImpl.java b/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceImpl.java index 8f453c9..491ce10 100644 --- a/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceImpl.java +++ b/viewsh-module-ops/viewsh-module-security-biz/src/main/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceImpl.java @@ -163,12 +163,42 @@ public class SecurityOrderServiceImpl implements SecurityOrderService { OpsOrderDO order = getOrderOrThrow(orderId); validateOrderType(order); - // 状态转换 → COMPLETED(扩展表时间 + 业务日志由 EventListener 统一记录,主表 endTime 由状态机统一设置) - orderStateMachine.transition(order, WorkOrderStatusEnum.COMPLETED, - OperatorTypeEnum.SYSTEM, null, - StrUtil.isNotBlank(remark) ? remark : "系统自动完单"); + String effectiveRemark = StrUtil.isNotBlank(remark) ? remark : "系统自动完单"; + WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.valueOf(order.getStatus()); - log.info("安保工单自动完单: orderId={}", orderId); + // 1. 已经是终态,幂等跳过 + if (currentStatus == WorkOrderStatusEnum.COMPLETED || currentStatus == WorkOrderStatusEnum.CANCELLED) { + log.info("安保工单已处于终态,跳过自动完单: orderId={}, status={}", orderId, currentStatus); + return; + } + // 2. PENDING:未派单,直接取消(PENDING → CANCELLED 规则已支持) + if (currentStatus == WorkOrderStatusEnum.PENDING) { + orderStateMachine.transition(order, WorkOrderStatusEnum.CANCELLED, + OperatorTypeEnum.SYSTEM, null, effectiveRemark + "(工单未派单)"); + } + // 3. 其他非终态(DISPATCHED / CONFIRMED / ARRIVED / PAUSED)→ 强制跳转 COMPLETED + else { + orderStateMachine.forceTransition(order, WorkOrderStatusEnum.COMPLETED, + OperatorTypeEnum.SYSTEM, null, effectiveRemark); + } + + log.info("安保工单自动结单: orderId={}, 原始状态={}", orderId, currentStatus); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void falseAlarmOrder(Long orderId) { + // 复用自动完单逻辑 + autoCompleteOrder(orderId, "误报"); + + // 更新扩展表:标记误报 + OpsOrderSecurityExtDO extUpdate = new OpsOrderSecurityExtDO(); + extUpdate.setOpsOrderId(orderId); + extUpdate.setFalseAlarm(true); + extUpdate.setResult("误报"); + securityExtMapper.insertOrUpdateSelective(extUpdate); + + log.info("安保工单误报标记: orderId={}", orderId); } @Override @@ -196,29 +226,6 @@ public class SecurityOrderServiceImpl implements SecurityOrderService { log.info("安保工单人工完单: orderId={}", req.getOrderId()); } - @Override - @Transactional(rollbackFor = Exception.class) - public void falseAlarmOrder(Long orderId, Long operatorId) { - OpsOrderDO order = getOrderOrThrow(orderId); - validateOrderType(order); - - // 如果 operatorId 为 null(open-api 调用),取已分配人员 - Long effectiveOperatorId = resolveOperatorId(orderId, operatorId); - - // 状态转换 → COMPLETED - orderStateMachine.transition(order, WorkOrderStatusEnum.COMPLETED, - OperatorTypeEnum.SECURITY_GUARD, effectiveOperatorId, "误报标记"); - - // 更新扩展表:标记误报 + 结果 - OpsOrderSecurityExtDO extUpdate = new OpsOrderSecurityExtDO(); - extUpdate.setOpsOrderId(orderId); - extUpdate.setFalseAlarm(true); - extUpdate.setResult("误报"); - securityExtMapper.insertOrUpdateSelective(extUpdate); - - log.info("安保工单误报标记: orderId={}", orderId); - } - @Override public OpsOrderSecurityExtDO getSecurityExt(Long opsOrderId) { return securityExtMapper.selectByOpsOrderId(opsOrderId); diff --git a/viewsh-module-ops/viewsh-module-security-biz/src/test/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceTest.java b/viewsh-module-ops/viewsh-module-security-biz/src/test/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceTest.java index 343183a..14d9676 100644 --- a/viewsh-module-ops/viewsh-module-security-biz/src/test/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceTest.java +++ b/viewsh-module-ops/viewsh-module-security-biz/src/test/java/com/viewsh/module/ops/security/service/securityorder/SecurityOrderServiceTest.java @@ -251,8 +251,8 @@ public class SecurityOrderServiceTest { // 执行 securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除"); - // 验证状态机调用 - verify(orderStateMachine).transition( + // 验证状态机调用(ARRIVED 非终态,走 forceTransition) + verify(orderStateMachine).forceTransition( eq(order), eq(WorkOrderStatusEnum.COMPLETED), eq(OperatorTypeEnum.SYSTEM), @@ -260,14 +260,7 @@ public class SecurityOrderServiceTest { eq("告警自动解除") ); - // 验证主表 endTime 更新 - ArgumentCaptor updateCaptor = ArgumentCaptor.forClass(OpsOrderDO.class); - verify(opsOrderMapper).updateById(updateCaptor.capture()); - OpsOrderDO updated = updateCaptor.getValue(); - assertEquals(TEST_ORDER_ID, updated.getId()); - assertNotNull(updated.getEndTime()); - - // 验证不再直接写扩展表时间 + // 验证不直接写扩展表 verify(securityExtMapper, never()).insertOrUpdateSelective(any()); } @@ -281,7 +274,7 @@ public class SecurityOrderServiceTest { securityOrderService.autoCompleteOrder(TEST_ORDER_ID, null); // 验证使用默认备注 - verify(orderStateMachine).transition( + verify(orderStateMachine).forceTransition( any(), eq(WorkOrderStatusEnum.COMPLETED), eq(OperatorTypeEnum.SYSTEM), isNull(), eq("系统自动完单") @@ -298,7 +291,7 @@ public class SecurityOrderServiceTest { securityOrderService.autoCompleteOrder(TEST_ORDER_ID, " "); // 验证使用默认备注 - verify(orderStateMachine).transition( + verify(orderStateMachine).forceTransition( any(), eq(WorkOrderStatusEnum.COMPLETED), eq(OperatorTypeEnum.SYSTEM), isNull(), eq("系统自动完单") @@ -311,6 +304,87 @@ public class SecurityOrderServiceTest { () -> securityOrderService.autoCompleteOrder(999L, "test")); } + @Test + void testAutoCompleteOrder_Pending_ShouldCancel() { + // 准备:PENDING 状态工单 + OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.PENDING); + orderDB.put(TEST_ORDER_ID, order); + + // 执行 + securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除"); + + // 验证:走正常 transition → CANCELLED + verify(orderStateMachine).transition( + eq(order), + eq(WorkOrderStatusEnum.CANCELLED), + eq(OperatorTypeEnum.SYSTEM), + isNull(), + eq("告警自动解除(工单未派单)") + ); + // 不应调用 forceTransition + verify(orderStateMachine, never()).forceTransition(any(), any(), any(), any(), any()); + } + + @Test + void testAutoCompleteOrder_Dispatched_ShouldForceComplete() { + // 准备:DISPATCHED 状态工单 + OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.DISPATCHED); + orderDB.put(TEST_ORDER_ID, order); + + // 执行 + securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除"); + + // 验证:走 forceTransition → COMPLETED + verify(orderStateMachine).forceTransition( + eq(order), + eq(WorkOrderStatusEnum.COMPLETED), + eq(OperatorTypeEnum.SYSTEM), + isNull(), + eq("告警自动解除") + ); + } + + @Test + void testAutoCompleteOrder_AlreadyCompleted_Idempotent() { + // 准备:已完成工单 + OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.COMPLETED); + orderDB.put(TEST_ORDER_ID, order); + + // 执行:不应抛异常 + securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除"); + + // 验证:状态机未被调用 + verify(orderStateMachine, never()).transition(any(), any(), any(), any(), any()); + verify(orderStateMachine, never()).forceTransition(any(), any(), any(), any(), any()); + } + + @Test + void testFalseAlarmOrder_DelegatesToAutoCompleteAndMarksExt() { + // 准备:DISPATCHED 状态工单 + OpsOrderDO order = buildSecurityOrder(TEST_ORDER_ID, WorkOrderStatusEnum.DISPATCHED); + orderDB.put(TEST_ORDER_ID, order); + + // 执行 + securityOrderService.falseAlarmOrder(TEST_ORDER_ID); + + // 验证:forceTransition 被调用(通过 autoCompleteOrder 委托) + verify(orderStateMachine).forceTransition( + eq(order), + eq(WorkOrderStatusEnum.COMPLETED), + eq(OperatorTypeEnum.SYSTEM), + isNull(), + eq("误报") + ); + + // 验证:扩展表写入误报标记 + ArgumentCaptor extCaptor = ArgumentCaptor.forClass(OpsOrderSecurityExtDO.class); + verify(securityExtMapper).insertOrUpdateSelective(extCaptor.capture()); + OpsOrderSecurityExtDO extUpdate = extCaptor.getValue(); + assertEquals(TEST_ORDER_ID, extUpdate.getOpsOrderId()); + assertTrue(extUpdate.getFalseAlarm()); + assertEquals("误报", extUpdate.getResult()); + } + // ==================== 人工完单测试 ==================== @Test