feat(ops): 安保工单新增误报标记、完善确认/完单接口支持 open-api 场景

- 新增 falseAlarmOrder 方法,标记误报并完成工单
- confirmOrder/manualCompleteOrder 支持 operatorId 为 null(open-api 自动取已分配人员)
- 新增 resolveOperatorId 辅助方法,null 时记录 warn 日志
- createSecurityOrder 移除 location 透传,改用 AreaPathBuilder 自动拼接
- 消除 createSecurityOrder 中 area 重复查询(校验 + buildPath 共用同一 DO)
- OpsOrderSecurityExtDO 新增 falseAlarm 字段
- SecurityOrderCompleteReqDTO.operatorId 移除 @NotNull 约束

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
lzh
2026-03-15 10:31:50 +08:00
parent 825c8eecca
commit f32315f790
5 changed files with 74 additions and 16 deletions

View File

@@ -81,6 +81,10 @@ public class OpsOrderSecurityExtDO extends BaseDO {
* 处理结果图片URLJSON数组
*/
private String resultImgUrls;
/**
* 是否误报true=误报)
*/
private Boolean falseAlarm;
// ==================== 关键时间点 ====================

View File

@@ -29,9 +29,8 @@ public class SecurityOrderCompleteReqDTO {
private List<String> resultImgUrls;
/**
* 操作人ID由 Controller 层填充)
* 操作人ID由 Controller 层填充open-api 场景可为 nullService 层会自动取已分配人员
*/
@NotNull(message = "操作人ID不能为空")
private Long operatorId;
}

View File

@@ -28,8 +28,6 @@ public class SecurityOrderCreateReqDTO {
@NotNull(message = "区域ID不能为空")
private Long areaId;
private String location;
// ==================== 告警来源 ====================
private String alarmId;

View File

@@ -44,6 +44,14 @@ public interface SecurityOrderService {
*/
void manualCompleteOrder(SecurityOrderCompleteReqDTO req);
/**
* 误报标记(将工单标记为误报并完成)
*
* @param orderId 工单ID
* @param operatorId 操作人ID可为 null为 null 时取已分配人员)
*/
void falseAlarmOrder(Long orderId, Long operatorId);
/**
* 根据工单ID查询安保扩展信息
*

View File

@@ -1,8 +1,9 @@
package com.viewsh.module.ops.security.service.securityorder;
import cn.hutool.core.util.StrUtil;
import com.viewsh.module.ops.core.event.OrderEventPublisher;
import com.viewsh.module.ops.core.event.OrderCreatedEvent;
import com.viewsh.module.ops.core.event.OrderEventPublisher;
import com.viewsh.module.ops.dal.dataobject.area.OpsBusAreaDO;
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
import com.viewsh.module.ops.dal.mysql.area.OpsBusAreaMapper;
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
@@ -11,17 +12,14 @@ import com.viewsh.module.ops.enums.PriorityEnum;
import com.viewsh.module.ops.enums.SourceTypeEnum;
import com.viewsh.module.ops.enums.WorkOrderStatusEnum;
import com.viewsh.module.ops.enums.WorkOrderTypeEnum;
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.enumeration.EventDomain;
import com.viewsh.module.ops.infrastructure.log.enumeration.LogModule;
import com.viewsh.module.ops.infrastructure.log.enumeration.LogType;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord;
import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder;
// 注意confirm/complete 的业务日志由 SecurityOrderEventListener 统一记录
// 本类仅记录 CREATE 日志(创建不经过状态变更事件)
import static com.viewsh.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.viewsh.module.ops.enums.ErrorCodeConstants.*;
import com.viewsh.module.ops.infrastructure.id.OrderIdGenerator;
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.fsm.OrderStateMachine;
@@ -30,6 +28,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import static com.viewsh.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.viewsh.module.ops.enums.ErrorCodeConstants.*;
/**
* 安保工单服务实现
*
@@ -57,6 +58,9 @@ public class SecurityOrderServiceImpl implements SecurityOrderService {
@Resource
private OpsBusAreaMapper opsBusAreaMapper;
@Resource
private AreaPathBuilder areaPathBuilder;
@Resource
private OrderStateMachine orderStateMachine;
@@ -69,7 +73,8 @@ public class SecurityOrderServiceImpl implements SecurityOrderService {
@Transactional(rollbackFor = Exception.class)
public Long createSecurityOrder(SecurityOrderCreateReqDTO createReq) {
// 0. 校验区域是否存在
if (opsBusAreaMapper.selectById(createReq.getAreaId()) == null) {
OpsBusAreaDO area = opsBusAreaMapper.selectById(createReq.getAreaId());
if (area == null) {
throw exception(AREA_NOT_FOUND);
}
@@ -82,7 +87,7 @@ public class SecurityOrderServiceImpl implements SecurityOrderService {
? createReq.getSourceType()
: (StrUtil.isNotBlank(createReq.getAlarmId()) ? SourceTypeEnum.ALARM.getType() : SourceTypeEnum.MANUAL.getType());
// 3. 构建主表记录
// 3. 构建主表记录location 由 areaId 自动拼接)
OpsOrderDO order = OpsOrderDO.builder()
.id(orderId)
.orderCode(orderCode)
@@ -93,7 +98,7 @@ public class SecurityOrderServiceImpl implements SecurityOrderService {
.priority(createReq.getPriority() != null ? createReq.getPriority() : PriorityEnum.P2.getPriority())
.status(WorkOrderStatusEnum.PENDING.getStatus())
.areaId(createReq.getAreaId())
.location(createReq.getLocation())
.location(areaPathBuilder.buildPath(area))
.build();
opsOrderMapper.insert(order);
@@ -142,11 +147,14 @@ public class SecurityOrderServiceImpl implements SecurityOrderService {
OpsOrderDO order = getOrderOrThrow(orderId);
validateOrderType(order);
// 如果 userId 为 nullopen-api 调用),取已分配人员
Long effectiveUserId = resolveOperatorId(orderId, userId);
// 状态转换DISPATCHED → CONFIRMED扩展表时间 + 业务日志由 EventListener 统一记录)
orderStateMachine.transition(order, WorkOrderStatusEnum.CONFIRMED,
OperatorTypeEnum.SECURITY_GUARD, userId, "安保人员确认接单");
OperatorTypeEnum.SECURITY_GUARD, effectiveUserId, "安保人员确认接单");
log.info("安保工单确认: orderId={}, userId={}", orderId, userId);
log.info("安保工单确认: orderId={}, userId={}", orderId, effectiveUserId);
}
@Override
@@ -169,9 +177,12 @@ public class SecurityOrderServiceImpl implements SecurityOrderService {
OpsOrderDO order = getOrderOrThrow(req.getOrderId());
validateOrderType(order);
// 如果 operatorId 为 nullopen-api 调用),取已分配人员
Long effectiveOperatorId = resolveOperatorId(req.getOrderId(), req.getOperatorId());
// 状态转换 → COMPLETED扩展表 completedTime + 业务日志由 EventListener 统一记录,主表 endTime 由状态机统一设置)
orderStateMachine.transition(order, WorkOrderStatusEnum.COMPLETED,
OperatorTypeEnum.SECURITY_GUARD, req.getOperatorId(), "安保人员提交处理结果");
OperatorTypeEnum.SECURITY_GUARD, effectiveOperatorId, "安保人员提交处理结果");
// 更新扩展表:结果 + 图片
OpsOrderSecurityExtDO extUpdate = new OpsOrderSecurityExtDO();
@@ -185,6 +196,29 @@ 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 为 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
public OpsOrderSecurityExtDO getSecurityExt(Long opsOrderId) {
return securityExtMapper.selectByOpsOrderId(opsOrderId);
@@ -206,4 +240,19 @@ public class SecurityOrderServiceImpl implements SecurityOrderService {
}
}
/**
* 解析操作人ID如果传入为 null则取扩展表中已分配的安保人员
*/
private Long resolveOperatorId(Long orderId, Long operatorId) {
if (operatorId != null) {
return operatorId;
}
OpsOrderSecurityExtDO ext = securityExtMapper.selectByOpsOrderId(orderId);
Long assignedUserId = ext != null ? ext.getAssignedUserId() : null;
if (assignedUserId == null) {
log.warn("工单未分配安保人员,操作人为空: orderId={}", orderId);
}
return assignedUserId;
}
}