fix(security): sendCard 改为 DISPATCHED 状态变更时发送 + @EventListener
- 将 sendCard 从 onOrderCreated 移至 handleDispatched - 使用 @EventListener 替代 @TransactionalEventListener(AFTER_COMMIT) 确保 autoDispatchNext 场景的 DISPATCHED 事件也能触发 - PAUSED → DISPATCHED 恢复场景跳过重发 - 参考保洁模块 CleanOrderEventListener 的实现模式 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityE
|
||||
import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.event.TransactionPhase;
|
||||
@@ -90,13 +91,13 @@ public class SecurityOrderEventListener {
|
||||
DispatchResult result = dispatchEngine.dispatch(context);
|
||||
|
||||
if (result.isSuccess()) {
|
||||
log.info("安保工单自动派单完成: orderId={}, assigneeId={}", event.getOrderId(), result.getAssigneeId());
|
||||
log.info("安保工单自动派单完成: orderId={}, assigneeId={}, path={}",
|
||||
event.getOrderId(), result.getAssigneeId(), result.getPath());
|
||||
// 记录派单成功日志
|
||||
recordLog(EventDomain.DISPATCH, LogType.ORDER_DISPATCHED,
|
||||
"自动派单成功,分配给: " + result.getAssigneeName(),
|
||||
event.getOrderId(), result.getAssigneeId());
|
||||
// 派单成功后发送企微卡片通知
|
||||
vspNotifyListener.sendCard(event, result);
|
||||
// 企微卡片通知统一在 handleDispatched 中发送(状态变为 DISPATCHED 时触发)
|
||||
} else {
|
||||
log.warn("安保工单自动派单失败: orderId={}, reason={}", event.getOrderId(), result.getMessage());
|
||||
// 记录派单失败日志
|
||||
@@ -116,8 +117,14 @@ public class SecurityOrderEventListener {
|
||||
|
||||
/**
|
||||
* 状态变更事件 - 记录扩展表时间点 + 业务日志
|
||||
* <p>
|
||||
* 使用 @EventListener 而非 @TransactionalEventListener(AFTER_COMMIT),
|
||||
* 确保 autoDispatchNext 在 AFTER_COMMIT 中派发下一单时发布的 DISPATCHED
|
||||
* 事件也能被捕获。各 handler 方法使用 @Async 异步执行。
|
||||
* <p>
|
||||
* 参考保洁模块 CleanOrderEventListener 的实现模式。
|
||||
*/
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
||||
@EventListener
|
||||
public void onOrderStateChanged(OrderStateChangedEvent event) {
|
||||
if (!ORDER_TYPE_SECURITY.equals(event.getOrderType())) {
|
||||
return;
|
||||
@@ -205,6 +212,11 @@ public class SecurityOrderEventListener {
|
||||
}
|
||||
|
||||
recordLog(EventDomain.DISPATCH, LogType.ORDER_DISPATCHED, message, orderId, assigneeId);
|
||||
|
||||
// 3. 工单推送时发送企微卡片通知(暂停恢复不重发,人员已知晓该工单)
|
||||
if (event.getOldStatus() != WorkOrderStatusEnum.PAUSED) {
|
||||
vspNotifyListener.sendCardByOrderId(orderId, assigneeId);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleConfirmed(Long orderId, OrderStateChangedEvent event) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.viewsh.module.ops.security.integration.listener;
|
||||
|
||||
import com.viewsh.module.ops.core.dispatch.model.DispatchResult;
|
||||
import com.viewsh.module.ops.core.event.OrderCreatedEvent;
|
||||
import com.viewsh.module.ops.core.event.OrderStateChangedEvent;
|
||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||
import com.viewsh.module.ops.enums.WorkOrderTypeEnum;
|
||||
@@ -9,6 +7,8 @@ import com.viewsh.module.ops.infrastructure.vsp.VspNotifyClient;
|
||||
import com.viewsh.module.ops.infrastructure.vsp.WechatUserIdResolver;
|
||||
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSendCardReqDTO;
|
||||
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSyncStatusReqDTO;
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
|
||||
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
||||
import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper;
|
||||
import com.viewsh.module.ops.service.area.OpsBusAreaService;
|
||||
@@ -47,6 +47,9 @@ public class SecurityOrderVspNotifyListener {
|
||||
@Resource
|
||||
private OpsBusAreaService opsBusAreaService;
|
||||
|
||||
@Resource
|
||||
private OpsOrderMapper opsOrderMapper;
|
||||
|
||||
@Resource
|
||||
private OpsOrderSecurityExtMapper securityExtMapper;
|
||||
|
||||
@@ -67,43 +70,49 @@ public class SecurityOrderVspNotifyListener {
|
||||
// ==================== 核心方法 ====================
|
||||
|
||||
/**
|
||||
* 派单成功后发送企微工单卡片
|
||||
* 工单推送(DISPATCHED)时发送企微工单卡片
|
||||
* <p>
|
||||
* 由 {@link SecurityOrderEventListener#onOrderCreated} 在派单成功后调用
|
||||
* 由 {@link SecurityOrderEventListener#onOrderStateChanged} 在工单状态变为 DISPATCHED 时调用,
|
||||
* 统一覆盖直接派单和从队列推送两种场景。
|
||||
*/
|
||||
public void sendCard(OrderCreatedEvent event, DispatchResult result) {
|
||||
public void sendCardByOrderId(Long orderId, Long assigneeId) {
|
||||
try {
|
||||
OpsOrderSecurityExtDO ext = securityExtMapper.selectByOpsOrderId(event.getOrderId());
|
||||
OpsOrderSecurityExtDO ext = securityExtMapper.selectByOpsOrderId(orderId);
|
||||
if (ext == null || ext.getAlarmId() == null) {
|
||||
return; // 非告警来源工单不发卡片
|
||||
}
|
||||
|
||||
String wechatUserId = wechatUserIdResolver.resolve(result.getAssigneeId());
|
||||
String wechatUserId = wechatUserIdResolver.resolve(assigneeId);
|
||||
if (wechatUserId == null) {
|
||||
log.warn("[sendCard] 未找到企微userId, assigneeId={}, orderId={}",
|
||||
result.getAssigneeId(), event.getOrderId());
|
||||
log.warn("[sendCard] 未找到企微userId, assigneeId={}, orderId={}", assigneeId, orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
String areaName = resolveAreaName(event.getAreaId());
|
||||
OpsOrderDO order = opsOrderMapper.selectById(orderId);
|
||||
if (order == null) {
|
||||
log.warn("[sendCard] 工单不存在, orderId={}", orderId);
|
||||
return;
|
||||
}
|
||||
|
||||
String areaName = resolveAreaName(order.getAreaId());
|
||||
|
||||
VspSendCardReqDTO req = VspSendCardReqDTO.builder()
|
||||
.alarmId(ext.getAlarmId())
|
||||
.orderId(String.valueOf(event.getOrderId()))
|
||||
.orderId(String.valueOf(orderId))
|
||||
.userIds(List.of(wechatUserId))
|
||||
.title(event.getTitle())
|
||||
.title(order.getTitle())
|
||||
.areaName(areaName)
|
||||
.cameraName(ext.getCameraName())
|
||||
.eventTime(event.getCreateTime() != null
|
||||
? event.getCreateTime().format(EVENT_TIME_FMT)
|
||||
.eventTime(order.getCreateTime() != null
|
||||
? order.getCreateTime().format(EVENT_TIME_FMT)
|
||||
: null)
|
||||
.level(event.getPriority())
|
||||
.level(order.getPriority())
|
||||
.snapshotUrl(ext.getImageUrl())
|
||||
.build();
|
||||
|
||||
vspNotifyClient.sendCard(req);
|
||||
} catch (Exception e) {
|
||||
log.error("[sendCard] 发送企微卡片异常, orderId={}", event.getOrderId(), e);
|
||||
log.error("[sendCard] 发送企微卡片异常, orderId={}", orderId, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.viewsh.module.ops.core.dispatch.model.OrderDispatchContext;
|
||||
import com.viewsh.module.ops.core.event.OrderCompletedEvent;
|
||||
import com.viewsh.module.ops.core.event.OrderCreatedEvent;
|
||||
import com.viewsh.module.ops.core.event.OrderStateChangedEvent;
|
||||
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
|
||||
import com.viewsh.module.ops.enums.PriorityEnum;
|
||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
||||
@@ -34,9 +35,15 @@ public class SecurityOrderEventListenerTest {
|
||||
@Mock
|
||||
private OpsOrderSecurityExtMapper securityExtMapper;
|
||||
|
||||
@Mock
|
||||
private OpsOrderMapper opsOrderMapper;
|
||||
|
||||
@Mock
|
||||
private DispatchEngine dispatchEngine;
|
||||
|
||||
@Mock
|
||||
private SecurityOrderVspNotifyListener vspNotifyListener;
|
||||
|
||||
private static final Long TEST_ORDER_ID = 10001L;
|
||||
private static final String TEST_ORDER_CODE = "SECURITY-20260310-0001";
|
||||
|
||||
@@ -141,9 +148,6 @@ public class SecurityOrderEventListenerTest {
|
||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||
WorkOrderStatusEnum.ARRIVED, WorkOrderStatusEnum.COMPLETED);
|
||||
|
||||
OpsOrderSecurityExtDO existingExt = OpsOrderSecurityExtDO.builder()
|
||||
.id(1L).opsOrderId(TEST_ORDER_ID).build();
|
||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(existingExt);
|
||||
lenient().when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||
|
||||
// 执行
|
||||
@@ -157,20 +161,6 @@ public class SecurityOrderEventListenerTest {
|
||||
assertNotNull(ext.getCompletedTime());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOnOrderStateChanged_Completed_NoExt_Skipped() {
|
||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||
WorkOrderStatusEnum.ARRIVED, WorkOrderStatusEnum.COMPLETED);
|
||||
|
||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(null);
|
||||
|
||||
// 执行
|
||||
listener.onOrderStateChanged(event);
|
||||
|
||||
// 验证:扩展记录不存在时不写入
|
||||
verify(securityExtMapper, never()).insertOrUpdateSelective(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testOnOrderStateChanged_CleanType_Ignored() {
|
||||
OrderStateChangedEvent event = OrderStateChangedEvent.builder()
|
||||
|
||||
@@ -1,28 +1,30 @@
|
||||
package com.viewsh.module.ops.security.integration.listener;
|
||||
|
||||
import com.viewsh.framework.common.pojo.CommonResult;
|
||||
import com.viewsh.module.ops.core.dispatch.DispatchEngine;
|
||||
import com.viewsh.module.ops.core.dispatch.model.DispatchPath;
|
||||
import com.viewsh.module.ops.core.dispatch.model.DispatchResult;
|
||||
import com.viewsh.module.ops.core.event.OrderCreatedEvent;
|
||||
import com.viewsh.module.ops.core.event.OrderStateChangedEvent;
|
||||
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
|
||||
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
|
||||
import com.viewsh.module.ops.enums.PriorityEnum;
|
||||
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
|
||||
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
|
||||
import com.viewsh.module.ops.infrastructure.vsp.VspNotifyClient;
|
||||
import com.viewsh.module.ops.infrastructure.vsp.WechatUserIdResolver;
|
||||
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSendCardReqDTO;
|
||||
import com.viewsh.module.ops.security.dal.dataobject.workorder.OpsOrderSecurityExtDO;
|
||||
import com.viewsh.module.ops.security.dal.mysql.workorder.OpsOrderSecurityExtMapper;
|
||||
import com.viewsh.module.ops.infrastructure.vsp.VspNotifyClient;
|
||||
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSendCardReqDTO;
|
||||
import com.viewsh.module.ops.infrastructure.vsp.dto.VspSyncStatusReqDTO;
|
||||
import com.viewsh.module.ops.service.area.OpsBusAreaService;
|
||||
import com.viewsh.module.ops.dal.dataobject.vo.area.OpsBusAreaRespVO;
|
||||
import com.viewsh.module.system.api.social.SocialUserApi;
|
||||
import com.viewsh.module.system.api.social.dto.SocialUserRespDTO;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@@ -31,21 +33,23 @@ import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/**
|
||||
* 安保工单事件监听器 - VSP 企微通知集成测试
|
||||
* 安保工单事件监听器 - VSP 企微通知时机测试
|
||||
* <p>
|
||||
* 验证 sendCard 在工单状态变为 DISPATCHED 时发送(而非创建时)
|
||||
*
|
||||
* @author lzh
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
public class SecurityOrderEventListenerVspTest {
|
||||
|
||||
@InjectMocks
|
||||
private SecurityOrderEventListener listener;
|
||||
private SecurityOrderVspNotifyListener vspNotifyListener;
|
||||
|
||||
@Mock
|
||||
private VspNotifyClient vspNotifyClient;
|
||||
|
||||
@Mock
|
||||
private SocialUserApi socialUserApi;
|
||||
private WechatUserIdResolver wechatUserIdResolver;
|
||||
|
||||
@Mock
|
||||
private OpsBusAreaService opsBusAreaService;
|
||||
@@ -53,6 +57,9 @@ public class SecurityOrderEventListenerVspTest {
|
||||
@Mock
|
||||
private OpsOrderSecurityExtMapper securityExtMapper;
|
||||
|
||||
@Mock
|
||||
private OpsOrderMapper opsOrderMapper;
|
||||
|
||||
@Mock
|
||||
private DispatchEngine dispatchEngine;
|
||||
|
||||
@@ -65,11 +72,28 @@ public class SecurityOrderEventListenerVspTest {
|
||||
private static final String TEST_WECHAT_ID = "test_wechat_id";
|
||||
private static final String TEST_ALARM_ID = "alarm-001";
|
||||
|
||||
// ==================== onOrderCreated VSP 通知测试 ====================
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
// 构建真实 vspNotifyListener 并注入 mock 依赖
|
||||
vspNotifyListener = new SecurityOrderVspNotifyListener();
|
||||
ReflectionTestUtils.setField(vspNotifyListener, "vspNotifyClient", vspNotifyClient);
|
||||
ReflectionTestUtils.setField(vspNotifyListener, "wechatUserIdResolver", wechatUserIdResolver);
|
||||
ReflectionTestUtils.setField(vspNotifyListener, "opsBusAreaService", opsBusAreaService);
|
||||
ReflectionTestUtils.setField(vspNotifyListener, "opsOrderMapper", opsOrderMapper);
|
||||
ReflectionTestUtils.setField(vspNotifyListener, "securityExtMapper", securityExtMapper);
|
||||
|
||||
// 构建真实 listener 并注入依赖
|
||||
listener = new SecurityOrderEventListener();
|
||||
ReflectionTestUtils.setField(listener, "securityExtMapper", securityExtMapper);
|
||||
ReflectionTestUtils.setField(listener, "dispatchEngine", dispatchEngine);
|
||||
ReflectionTestUtils.setField(listener, "eventLogRecorder", eventLogRecorder);
|
||||
ReflectionTestUtils.setField(listener, "vspNotifyListener", vspNotifyListener);
|
||||
}
|
||||
|
||||
// ==================== onOrderCreated 不再发送卡片 ====================
|
||||
|
||||
@Test
|
||||
void onOrderCreated_shouldSendCard_whenDispatchSuccessAndAlarmSource() {
|
||||
// 准备事件
|
||||
void onOrderCreated_shouldNotSendCard_whenEnqueueOnly() {
|
||||
OrderCreatedEvent event = OrderCreatedEvent.builder()
|
||||
.orderId(TEST_ORDER_ID)
|
||||
.orderType("SECURITY")
|
||||
@@ -77,36 +101,77 @@ public class SecurityOrderEventListenerVspTest {
|
||||
.title("入侵告警")
|
||||
.areaId(100L)
|
||||
.priority(PriorityEnum.P1.getPriority())
|
||||
.createTime(LocalDateTime.of(2026, 3, 24, 10, 0))
|
||||
.build();
|
||||
|
||||
// 派单成功
|
||||
DispatchResult dispatchResult = DispatchResult.success("ok", TEST_ASSIGNEE_ID, "张三", null, null);
|
||||
when(dispatchEngine.dispatch(any())).thenReturn(dispatchResult);
|
||||
DispatchResult result = DispatchResult.success("ok", TEST_ASSIGNEE_ID, "张三",
|
||||
DispatchPath.ENQUEUE_ONLY, 1L);
|
||||
when(dispatchEngine.dispatch(any())).thenReturn(result);
|
||||
|
||||
// 告警来源工单扩展记录
|
||||
listener.onOrderCreated(event);
|
||||
|
||||
// onOrderCreated 不发卡片
|
||||
verify(vspNotifyClient, never()).sendCard(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void onOrderCreated_shouldNotSendCard_whenDirectDispatch() {
|
||||
OrderCreatedEvent event = OrderCreatedEvent.builder()
|
||||
.orderId(TEST_ORDER_ID)
|
||||
.orderType("SECURITY")
|
||||
.orderCode(TEST_ORDER_CODE)
|
||||
.title("入侵告警")
|
||||
.areaId(100L)
|
||||
.priority(PriorityEnum.P1.getPriority())
|
||||
.build();
|
||||
|
||||
DispatchResult result = DispatchResult.success("ok", TEST_ASSIGNEE_ID, "张三",
|
||||
DispatchPath.DIRECT_DISPATCH, null);
|
||||
when(dispatchEngine.dispatch(any())).thenReturn(result);
|
||||
|
||||
listener.onOrderCreated(event);
|
||||
|
||||
// 即使直接派单,onOrderCreated 也不发卡片(由 handleDispatched 处理)
|
||||
verify(vspNotifyClient, never()).sendCard(any());
|
||||
}
|
||||
|
||||
// ==================== handleDispatched 发送卡片(端到端验证) ====================
|
||||
|
||||
@Test
|
||||
void onOrderStateChanged_shouldSendCard_whenDispatched() {
|
||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||
WorkOrderStatusEnum.QUEUED, WorkOrderStatusEnum.DISPATCHED);
|
||||
event.addPayload("assigneeId", TEST_ASSIGNEE_ID);
|
||||
event.addPayload("assigneeName", "张三");
|
||||
event.addPayload("assigneePhone", "13800138000");
|
||||
|
||||
// mock:告警来源工单
|
||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
||||
.id(1L)
|
||||
.opsOrderId(TEST_ORDER_ID)
|
||||
.alarmId(TEST_ALARM_ID)
|
||||
.cameraName("大堂摄像头")
|
||||
.id(1L).opsOrderId(TEST_ORDER_ID)
|
||||
.alarmId(TEST_ALARM_ID).cameraName("大堂摄像头")
|
||||
.imageUrl("http://example.com/snapshot.jpg")
|
||||
.build();
|
||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
||||
when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||
|
||||
// 企微 userId 查询
|
||||
SocialUserRespDTO socialUser = new SocialUserRespDTO();
|
||||
socialUser.setOpenid(TEST_WECHAT_ID);
|
||||
when(socialUserApi.getSocialUserByUserId(anyInt(), eq(TEST_ASSIGNEE_ID), anyInt()))
|
||||
.thenReturn(CommonResult.success(socialUser));
|
||||
// mock:企微 userId
|
||||
when(wechatUserIdResolver.resolve(TEST_ASSIGNEE_ID)).thenReturn(TEST_WECHAT_ID);
|
||||
|
||||
// 区域名称
|
||||
// mock:工单主表
|
||||
OpsOrderDO order = new OpsOrderDO();
|
||||
order.setId(TEST_ORDER_ID);
|
||||
order.setTitle("入侵告警");
|
||||
order.setAreaId(100L);
|
||||
order.setPriority(PriorityEnum.P1.getPriority());
|
||||
order.setCreateTime(LocalDateTime.of(2026, 3, 24, 10, 0));
|
||||
when(opsOrderMapper.selectById(TEST_ORDER_ID)).thenReturn(order);
|
||||
|
||||
// mock:区域名称
|
||||
OpsBusAreaRespVO area = new OpsBusAreaRespVO();
|
||||
area.setAreaName("A栋大堂");
|
||||
when(opsBusAreaService.getArea(100L)).thenReturn(area);
|
||||
|
||||
// 执行
|
||||
listener.onOrderCreated(event);
|
||||
listener.onOrderStateChanged(event);
|
||||
|
||||
// 验证发送了企微卡片
|
||||
ArgumentCaptor<VspSendCardReqDTO> captor = ArgumentCaptor.forClass(VspSendCardReqDTO.class);
|
||||
@@ -121,153 +186,49 @@ public class SecurityOrderEventListenerVspTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void onOrderCreated_shouldNotSendCard_whenNoAlarmId() {
|
||||
// 准备事件
|
||||
OrderCreatedEvent event = OrderCreatedEvent.builder()
|
||||
.orderId(TEST_ORDER_ID)
|
||||
.orderType("SECURITY")
|
||||
.orderCode(TEST_ORDER_CODE)
|
||||
.title("手动巡查工单")
|
||||
.areaId(100L)
|
||||
.priority(PriorityEnum.P2.getPriority())
|
||||
.build();
|
||||
void onOrderStateChanged_shouldNotSendCard_whenNoAlarmId() {
|
||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||
WorkOrderStatusEnum.QUEUED, WorkOrderStatusEnum.DISPATCHED);
|
||||
event.addPayload("assigneeId", TEST_ASSIGNEE_ID);
|
||||
event.addPayload("assigneeName", "张三");
|
||||
|
||||
// 派单成功
|
||||
DispatchResult dispatchResult = DispatchResult.success("ok", TEST_ASSIGNEE_ID, "张三", null, null);
|
||||
when(dispatchEngine.dispatch(any())).thenReturn(dispatchResult);
|
||||
|
||||
// 手动工单无告警ID
|
||||
// 非告警工单
|
||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
||||
.id(1L)
|
||||
.opsOrderId(TEST_ORDER_ID)
|
||||
.alarmId(null) // 无告警ID
|
||||
.build();
|
||||
.id(1L).opsOrderId(TEST_ORDER_ID).alarmId(null).build();
|
||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
||||
when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||
|
||||
// 执行
|
||||
listener.onOrderCreated(event);
|
||||
listener.onOrderStateChanged(event);
|
||||
|
||||
// 验证不发送卡片
|
||||
verify(vspNotifyClient, never()).sendCard(any());
|
||||
}
|
||||
|
||||
// ==================== onOrderStateChanged VSP 同步测试 ====================
|
||||
|
||||
@Test
|
||||
void onOrderStateChanged_shouldSyncConfirmed() {
|
||||
void onOrderStateChanged_shouldNotSendCard_whenConfirmed() {
|
||||
// CONFIRMED 状态不发卡片
|
||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||
WorkOrderStatusEnum.DISPATCHED, WorkOrderStatusEnum.CONFIRMED);
|
||||
|
||||
// 告警来源工单
|
||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
||||
.id(1L)
|
||||
.opsOrderId(TEST_ORDER_ID)
|
||||
.alarmId(TEST_ALARM_ID)
|
||||
.assignedUserId(TEST_ASSIGNEE_ID)
|
||||
.build();
|
||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
||||
lenient().when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||
when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||
|
||||
// 企微 userId 查询
|
||||
SocialUserRespDTO socialUser = new SocialUserRespDTO();
|
||||
socialUser.setOpenid(TEST_WECHAT_ID);
|
||||
when(socialUserApi.getSocialUserByUserId(anyInt(), eq(TEST_ASSIGNEE_ID), anyInt()))
|
||||
.thenReturn(CommonResult.success(socialUser));
|
||||
|
||||
// 执行
|
||||
listener.onOrderStateChanged(event);
|
||||
|
||||
// 验证同步状态
|
||||
ArgumentCaptor<VspSyncStatusReqDTO> captor = ArgumentCaptor.forClass(VspSyncStatusReqDTO.class);
|
||||
verify(vspNotifyClient).syncStatus(captor.capture());
|
||||
VspSyncStatusReqDTO req = captor.getValue();
|
||||
assertEquals(TEST_ALARM_ID, req.getAlarmId());
|
||||
assertEquals(String.valueOf(TEST_ORDER_ID), req.getOrderId());
|
||||
assertEquals("confirmed", req.getStatus());
|
||||
assertEquals(TEST_WECHAT_ID, req.getOperator());
|
||||
verify(vspNotifyClient, never()).sendCard(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void onOrderStateChanged_shouldSyncCompleted() {
|
||||
void onOrderStateChanged_shouldNotSendCard_whenResumedFromPause() {
|
||||
// 暂停恢复不重发卡片,人员已知晓该工单
|
||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||
WorkOrderStatusEnum.ARRIVED, WorkOrderStatusEnum.COMPLETED);
|
||||
WorkOrderStatusEnum.PAUSED, WorkOrderStatusEnum.DISPATCHED);
|
||||
event.addPayload("assigneeId", TEST_ASSIGNEE_ID);
|
||||
event.addPayload("assigneeName", "张三");
|
||||
|
||||
// 告警来源工单,非误报
|
||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
||||
.id(1L)
|
||||
.opsOrderId(TEST_ORDER_ID)
|
||||
.alarmId(TEST_ALARM_ID)
|
||||
.falseAlarm(false)
|
||||
.assignedUserId(TEST_ASSIGNEE_ID)
|
||||
.build();
|
||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
||||
lenient().when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||
when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||
|
||||
// 企微 userId 查询
|
||||
SocialUserRespDTO socialUser = new SocialUserRespDTO();
|
||||
socialUser.setOpenid(TEST_WECHAT_ID);
|
||||
when(socialUserApi.getSocialUserByUserId(anyInt(), eq(TEST_ASSIGNEE_ID), anyInt()))
|
||||
.thenReturn(CommonResult.success(socialUser));
|
||||
|
||||
// 执行
|
||||
listener.onOrderStateChanged(event);
|
||||
|
||||
// 验证同步状态为 completed
|
||||
ArgumentCaptor<VspSyncStatusReqDTO> captor = ArgumentCaptor.forClass(VspSyncStatusReqDTO.class);
|
||||
verify(vspNotifyClient).syncStatus(captor.capture());
|
||||
assertEquals("completed", captor.getValue().getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void onOrderStateChanged_shouldSyncFalseAlarm() {
|
||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||
WorkOrderStatusEnum.ARRIVED, WorkOrderStatusEnum.COMPLETED);
|
||||
|
||||
// 告警来源工单,标记为误报
|
||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
||||
.id(1L)
|
||||
.opsOrderId(TEST_ORDER_ID)
|
||||
.alarmId(TEST_ALARM_ID)
|
||||
.falseAlarm(true)
|
||||
.assignedUserId(TEST_ASSIGNEE_ID)
|
||||
.build();
|
||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
||||
lenient().when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||
|
||||
// 企微 userId 查询
|
||||
SocialUserRespDTO socialUser = new SocialUserRespDTO();
|
||||
socialUser.setOpenid(TEST_WECHAT_ID);
|
||||
when(socialUserApi.getSocialUserByUserId(anyInt(), eq(TEST_ASSIGNEE_ID), anyInt()))
|
||||
.thenReturn(CommonResult.success(socialUser));
|
||||
|
||||
// 执行
|
||||
listener.onOrderStateChanged(event);
|
||||
|
||||
// 验证同步状态为 false_alarm
|
||||
ArgumentCaptor<VspSyncStatusReqDTO> captor = ArgumentCaptor.forClass(VspSyncStatusReqDTO.class);
|
||||
verify(vspNotifyClient).syncStatus(captor.capture());
|
||||
assertEquals("false_alarm", captor.getValue().getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
void onOrderStateChanged_shouldNotSync_whenNoAlarmId() {
|
||||
OrderStateChangedEvent event = buildStateChangedEvent(
|
||||
WorkOrderStatusEnum.DISPATCHED, WorkOrderStatusEnum.CONFIRMED);
|
||||
|
||||
// 手动工单,无告警ID
|
||||
OpsOrderSecurityExtDO ext = OpsOrderSecurityExtDO.builder()
|
||||
.id(1L)
|
||||
.opsOrderId(TEST_ORDER_ID)
|
||||
.alarmId(null)
|
||||
.build();
|
||||
when(securityExtMapper.selectByOpsOrderId(TEST_ORDER_ID)).thenReturn(ext);
|
||||
lenient().when(securityExtMapper.insertOrUpdateSelective(any())).thenReturn(1);
|
||||
|
||||
// 执行
|
||||
listener.onOrderStateChanged(event);
|
||||
|
||||
// 验证不同步状态
|
||||
verify(vspNotifyClient, never()).syncStatus(any());
|
||||
verify(vspNotifyClient, never()).sendCard(any());
|
||||
}
|
||||
|
||||
// ==================== 辅助方法 ====================
|
||||
|
||||
Reference in New Issue
Block a user