fix(ops): 安保工单自动完单支持全状态处理
Some checks failed
Java CI with Maven / build (11) (push) Has been cancelled
Java CI with Maven / build (17) (push) Has been cancelled
Java CI with Maven / build (8) (push) Has been cancelled

autoCompleteOrder 根据当前状态分支处理:
- PENDING → transition CANCELLED(未派单,告警已解除)
- DISPATCHED/CONFIRMED/ARRIVED/PAUSED → forceTransition COMPLETED
- 已终态 → 幂等跳过

falseAlarmOrder 复用 autoCompleteOrder 逻辑,额外更新扩展表误报标记。
移除 falseAlarmOrder 未使用的 operatorId 参数。
补充 4 个单元测试覆盖新增分支。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-03-18 22:25:44 +08:00
parent e8a8baf62f
commit 92a51adcea
5 changed files with 134 additions and 47 deletions

View File

@@ -94,7 +94,7 @@ public class SecurityOrderController {
@Operation(summary = "误报标记", description = "将安保工单标记为误报并完成") @Operation(summary = "误报标记", description = "将安保工单标记为误报并完成")
@PreAuthorize("@ss.hasPermission('ops:security-order:complete')") @PreAuthorize("@ss.hasPermission('ops:security-order:complete')")
public CommonResult<Boolean> falseAlarmOrder(@Valid @RequestBody SecurityOrderIdReqVO reqVO) { public CommonResult<Boolean> falseAlarmOrder(@Valid @RequestBody SecurityOrderIdReqVO reqVO) {
securityOrderService.falseAlarmOrder(reqVO.getOrderId(), SecurityFrameworkUtils.getLoginUserId()); securityOrderService.falseAlarmOrder(reqVO.getOrderId());
return success(true); return success(true);
} }

View File

@@ -99,7 +99,7 @@ public class SecurityOrderOpenController {
@ApiSignature @ApiSignature
@PermitAll @PermitAll
public CommonResult<Boolean> falseAlarmOrder(@Valid @RequestBody SecurityOrderIdReqVO reqVO) { public CommonResult<Boolean> falseAlarmOrder(@Valid @RequestBody SecurityOrderIdReqVO reqVO) {
securityOrderService.falseAlarmOrder(reqVO.getOrderId(), null); securityOrderService.falseAlarmOrder(reqVO.getOrderId());
return success(true); return success(true);
} }

View File

@@ -30,7 +30,14 @@ public interface SecurityOrderService {
void confirmOrder(Long orderId, Long userId); void confirmOrder(Long orderId, Long userId);
/** /**
* 自动完单(对方系统调用,无需提交结果 * 自动完单(由边缘系统调用,告警自动解除
* <p>
* 根据当前工单状态:
* <ul>
* <li>PENDING未派单→ 直接取消</li>
* <li>DISPATCHED / CONFIRMED / ARRIVED / PAUSED → 强制完成</li>
* <li>已终态 → 幂等跳过</li>
* </ul>
* *
* @param orderId 工单ID * @param orderId 工单ID
* @param remark 备注 * @param remark 备注
@@ -45,12 +52,11 @@ public interface SecurityOrderService {
void manualCompleteOrder(SecurityOrderCompleteReqDTO req); void manualCompleteOrder(SecurityOrderCompleteReqDTO req);
/** /**
* 误报标记(将工单标记误报并完成 * 误报标记(复用自动完单逻辑,额外标记误报)
* *
* @param orderId 工单ID * @param orderId 工单ID
* @param operatorId 操作人ID可为 null为 null 时取已分配人员)
*/ */
void falseAlarmOrder(Long orderId, Long operatorId); void falseAlarmOrder(Long orderId);
/** /**
* 根据工单ID查询安保扩展信息 * 根据工单ID查询安保扩展信息

View File

@@ -163,12 +163,42 @@ public class SecurityOrderServiceImpl implements SecurityOrderService {
OpsOrderDO order = getOrderOrThrow(orderId); OpsOrderDO order = getOrderOrThrow(orderId);
validateOrderType(order); validateOrderType(order);
// 状态转换 → COMPLETED扩展表时间 + 业务日志由 EventListener 统一记录,主表 endTime 由状态机统一设置) String effectiveRemark = StrUtil.isNotBlank(remark) ? remark : "系统自动完单";
orderStateMachine.transition(order, WorkOrderStatusEnum.COMPLETED, WorkOrderStatusEnum currentStatus = WorkOrderStatusEnum.valueOf(order.getStatus());
OperatorTypeEnum.SYSTEM, null,
StrUtil.isNotBlank(remark) ? remark : "系统自动完单");
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 @Override
@@ -196,29 +226,6 @@ public class SecurityOrderServiceImpl implements SecurityOrderService {
log.info("安保工单人工完单: orderId={}", req.getOrderId()); log.info("安保工单人工完单: orderId={}", req.getOrderId());
} }
@Override
@Transactional(rollbackFor = Exception.class)
public void falseAlarmOrder(Long orderId, Long operatorId) {
OpsOrderDO order = getOrderOrThrow(orderId);
validateOrderType(order);
// 如果 operatorId 为 nullopen-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 @Override
public OpsOrderSecurityExtDO getSecurityExt(Long opsOrderId) { public OpsOrderSecurityExtDO getSecurityExt(Long opsOrderId) {
return securityExtMapper.selectByOpsOrderId(opsOrderId); return securityExtMapper.selectByOpsOrderId(opsOrderId);

View File

@@ -251,8 +251,8 @@ public class SecurityOrderServiceTest {
// 执行 // 执行
securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除"); securityOrderService.autoCompleteOrder(TEST_ORDER_ID, "告警自动解除");
// 验证状态机调用 // 验证状态机调用ARRIVED 非终态,走 forceTransition
verify(orderStateMachine).transition( verify(orderStateMachine).forceTransition(
eq(order), eq(order),
eq(WorkOrderStatusEnum.COMPLETED), eq(WorkOrderStatusEnum.COMPLETED),
eq(OperatorTypeEnum.SYSTEM), eq(OperatorTypeEnum.SYSTEM),
@@ -260,14 +260,7 @@ public class SecurityOrderServiceTest {
eq("告警自动解除") eq("告警自动解除")
); );
// 验证主表 endTime 更新 // 验证不直接写扩展表
ArgumentCaptor<OpsOrderDO> 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()); verify(securityExtMapper, never()).insertOrUpdateSelective(any());
} }
@@ -281,7 +274,7 @@ public class SecurityOrderServiceTest {
securityOrderService.autoCompleteOrder(TEST_ORDER_ID, null); securityOrderService.autoCompleteOrder(TEST_ORDER_ID, null);
// 验证使用默认备注 // 验证使用默认备注
verify(orderStateMachine).transition( verify(orderStateMachine).forceTransition(
any(), eq(WorkOrderStatusEnum.COMPLETED), any(), eq(WorkOrderStatusEnum.COMPLETED),
eq(OperatorTypeEnum.SYSTEM), isNull(), eq(OperatorTypeEnum.SYSTEM), isNull(),
eq("系统自动完单") eq("系统自动完单")
@@ -298,7 +291,7 @@ public class SecurityOrderServiceTest {
securityOrderService.autoCompleteOrder(TEST_ORDER_ID, " "); securityOrderService.autoCompleteOrder(TEST_ORDER_ID, " ");
// 验证使用默认备注 // 验证使用默认备注
verify(orderStateMachine).transition( verify(orderStateMachine).forceTransition(
any(), eq(WorkOrderStatusEnum.COMPLETED), any(), eq(WorkOrderStatusEnum.COMPLETED),
eq(OperatorTypeEnum.SYSTEM), isNull(), eq(OperatorTypeEnum.SYSTEM), isNull(),
eq("系统自动完单") eq("系统自动完单")
@@ -311,6 +304,87 @@ public class SecurityOrderServiceTest {
() -> securityOrderService.autoCompleteOrder(999L, "test")); () -> 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<OpsOrderSecurityExtDO> 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 @Test