feat(ops): refactor-order-operations

This commit is contained in:
lzh
2026-01-19 13:32:23 +08:00
parent 5419a949d4
commit 568d37a0be
31 changed files with 2806 additions and 1456 deletions

View File

@@ -58,10 +58,10 @@ public interface OrderLifecycleManager {
OrderTransitionResult enqueue(OrderTransitionRequest request);
/**
* 工单出队并派单QUEUED → DISPATCHED
* 工单出队并派单:PENDING/QUEUED → DISPATCHED
* <p>
* 状态转换:
* - 工单状态QUEUED → DISPATCHED
* - 工单状态:PENDING/QUEUED → DISPATCHED
* - 队列状态WAITING → PROCESSING
*
* @param request 状态转换请求

View File

@@ -0,0 +1,93 @@
package com.viewsh.module.ops.infrastructure.code;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
/**
* 工单编号生成器
* <p>
* 格式:{业务前缀}-{日期}-{序号}
* 例如CLEAN-20250119-0001, SECURITY-20250119-0001
* <p>
* 特性:
* - 使用 Redis 保证序号唯一性
* - 序号每日自动重置(按日期分 key
* - 不同业务类型独立计数
*
* @author lzh
*/
@Slf4j
@Service
public class OrderCodeGenerator {
private static final String KEY_PREFIX = "ops:order:code:";
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
private static final long DEFAULT_EXPIRE_DAYS = 7; // key 默认保留7天
@Resource
private StringRedisTemplate stringRedisTemplate;
/**
* 生成工单编号
*
* @param businessType 业务类型CLEAN、SECURITY、FACILITIES
* @return 工单编号,格式:{业务类型}-{日期}-{4位序号}
*/
public String generate(String businessType) {
if (businessType == null || businessType.isEmpty()) {
throw new IllegalArgumentException("Business type cannot be null or empty");
}
String dateStr = LocalDate.now().format(DATE_FORMATTER);
String key = KEY_PREFIX + businessType + ":" + dateStr;
// Redis 自增并获取新值
Long seq = stringRedisTemplate.opsForValue().increment(key);
// 首次创建时设置过期时间
if (seq != null && seq == 1) {
stringRedisTemplate.expire(key, DEFAULT_EXPIRE_DAYS, TimeUnit.DAYS);
}
if (seq == null) {
throw new RuntimeException("Failed to generate order code for business type: " + businessType);
}
String orderCode = String.format("%s-%s-%04d", businessType, dateStr, seq);
log.debug("生成工单编号: businessType={}, orderCode={}", businessType, orderCode);
return orderCode;
}
/**
* 获取指定业务类型当天的当前序号
*
* @param businessType 业务类型
* @return 当前序号如果不存在返回0
*/
public long getCurrentSeq(String businessType) {
String dateStr = LocalDate.now().format(DATE_FORMATTER);
String key = KEY_PREFIX + businessType + ":" + dateStr;
String value = stringRedisTemplate.opsForValue().get(key);
return value == null ? 0 : Long.parseLong(value);
}
/**
* 重置指定业务类型当天的序号(仅供测试或特殊场景使用)
*
* @param businessType 业务类型
*/
public void reset(String businessType) {
String dateStr = LocalDate.now().format(DATE_FORMATTER);
String key = KEY_PREFIX + businessType + ":" + dateStr;
stringRedisTemplate.delete(key);
log.warn("重置工单编号序号: businessType={}, date={}", businessType, dateStr);
}
}

View File

@@ -0,0 +1,55 @@
package com.viewsh.module.ops.infrastructure.id;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 工单ID生成器
* <p>
* 使用雪花算法,保证分布式环境下的全局唯一性。
* 适用于工单主键生成。
*
* @author lzh
*/
@Slf4j
@Component
public class OrderIdGenerator {
private final OrderIdProperties orderIdProperties;
private SnowflakeIdGenerator snowflake;
/**
* 构造函数,注入配置
*/
public OrderIdGenerator(OrderIdProperties orderIdProperties) {
this.orderIdProperties = orderIdProperties;
}
/**
* 初始化雪花算法生成器
*/
@PostConstruct
public void init() {
int datacenterId = orderIdProperties.getDatacenterId();
int machineId = orderIdProperties.getMachineId();
this.snowflake = new SnowflakeIdGenerator(datacenterId, machineId);
log.info("OrderIdGenerator初始化完成: datacenterId={}, machineId={}",
datacenterId, machineId);
}
/**
* 生成工单ID
*
* @return 唯一的Long类型ID
*/
public Long generate() {
if (snowflake == null) {
throw new IllegalStateException("OrderIdGenerator 尚未初始化");
}
return snowflake.nextId();
}
}

View File

@@ -0,0 +1,28 @@
package com.viewsh.module.ops.infrastructure.id;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 工单ID生成器<E68890><E599A8>
*
* @author lzh
*/
@Data
@Component
@ConfigurationProperties(prefix = "ops.order")
public class OrderIdProperties {
/**
* 数据中心ID0-31
* 默认值1
*/
private int datacenterId = 1;
/**
* 机器ID0-31
* 默认值1
*/
private int machineId = 1;
}

View File

@@ -0,0 +1,184 @@
package com.viewsh.module.ops.infrastructure.id;
/**
* 雪花算法ID生成器
* <p>
* ID<49><44><EFBFBD>64位Long
* - 1位符号位永远为0
* - 41位时间戳毫秒级可用69年
* - 5位数据中心ID0-31
* - 5位机器ID0-31
* - 12位序列号毫秒内计数0-4095
* <p>
* 特性:
* - 分布式环境全局唯一
* - 时间有序
* - 高性能单机每毫秒可生成4096个ID
*
* @author lzh
*/
public class SnowflakeIdGenerator {
/**
* 起始时间戳2024-01-01 00:00:00
* 可根据项目实际情况调整
*/
private static final long EPOCH = 1704067200000L;
/**
* 各部分位数
*/
private static final long DATACENTER_ID_BITS = 5L;
private static final long MACHINE_ID_BITS = 5L;
private static final long SEQUENCE_BITS = 12L;
/**
* 各部分最大值
*/
private static final long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS); // 31
private static final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS); // 31
private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BITS); // 4095
/**
* 各部分位移
*/
private static final long MACHINE_ID_SHIFT = SEQUENCE_BITS;
private static final long DATACENTER_ID_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS;
private static final long TIMESTAMP_SHIFT = SEQUENCE_BITS + MACHINE_ID_BITS + DATACENTER_ID_BITS;
/**
* 数据中心ID
*/
private final long datacenterId;
/**
* 机器ID
*/
private final long machineId;
/**
* 序列号
*/
private long sequence = 0L;
/**
* 上次生成ID的时间戳
*/
private long lastTimestamp = -1L;
/**
* 构造函数
*
* @param datacenterId 数据中心ID0-31
* @param machineId 机器ID0-31
* @throws IllegalArgumentException 如果ID超出范围
*/
public SnowflakeIdGenerator(long datacenterId, long machineId) {
if (datacenterId < 0 || datacenterId > MAX_DATACENTER_ID) {
throw new IllegalArgumentException(
String.format("Datacenter ID must be between 0 and %d", MAX_DATACENTER_ID));
}
if (machineId < 0 || machineId > MAX_MACHINE_ID) {
throw new IllegalArgumentException(
String.format("Machine ID must be between 0 and %d", MAX_MACHINE_ID));
}
this.datacenterId = datacenterId;
this.machineId = machineId;
}
/**
* 生成下一个ID线程安全
*
* @return 唯一的Long类型ID
*/
public synchronized long nextId() {
long timestamp = getCurrentTimestamp();
// 时钟回拨检查
if (timestamp < lastTimestamp) {
long offset = lastTimestamp - timestamp;
if (offset <= 5) {
// 少量时钟回拨,等待
try {
Thread.sleep(offset << 1);
timestamp = getCurrentTimestamp();
if (timestamp < lastTimestamp) {
throw new RuntimeException("Clock moved backwards. Refusing to generate ID");
}
} catch (InterruptedException e) {
throw new RuntimeException("Clock moved backwards. Waiting interrupted", e);
}
} else {
throw new RuntimeException("Clock moved backwards. Refusing to generate ID");
}
}
// 同一毫秒内,序列号自增
if (timestamp == lastTimestamp) {
sequence = (sequence + 1) & MAX_SEQUENCE;
// 序列号溢出,等待下一毫秒
if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp);
}
} else {
// 新的毫秒,序列号重置
sequence = 0L;
}
lastTimestamp = timestamp;
// 组装ID
return ((timestamp - EPOCH) << TIMESTAMP_SHIFT)
| (datacenterId << DATACENTER_ID_SHIFT)
| (machineId << MACHINE_ID_SHIFT)
| sequence;
}
/**
* 获取当前时间戳
*/
private long getCurrentTimestamp() {
return System.currentTimeMillis();
}
/**
* 等待下一毫秒
*/
private long tilNextMillis(long lastTimestamp) {
long timestamp = getCurrentTimestamp();
while (timestamp <= lastTimestamp) {
timestamp = getCurrentTimestamp();
}
return timestamp;
}
/**
* 解析ID获取时间戳信息
*
* @param id 雪花算法生成的ID
* @return 原始时间戳(毫秒)
*/
public static long parseTimestamp(long id) {
return ((id >> TIMESTAMP_SHIFT) & ~(-1L << 41L)) + EPOCH;
}
/**
* 解析ID获取数据中心ID
*
* @param id 雪花算法生成的ID
* @return 数据中心ID
*/
public static long parseDatacenterId(long id) {
return (id >> DATACENTER_ID_SHIFT) & MAX_DATACENTER_ID;
}
/**
* 解析ID获取机器ID
*
* @param id 雪花算法生成的ID
* @return 机器ID
*/
public static long parseMachineId(long id) {
return (id >> MACHINE_ID_SHIFT) & MAX_MACHINE_ID;
}
}

View File

@@ -0,0 +1,164 @@
package com.viewsh.module.ops.service;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 工单详情 VO基类
* <p>
* 支持多态,具体业务类型可继承此类添加扩展字段
*
* @author lzh
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderDetailVO {
/**
* 工单ID
*/
private Long id;
/**
* 工单编号
*/
private String orderCode;
/**
* 工单类型
*/
private String orderType;
/**
* 来源类型
*/
private String sourceType;
/**
* 工单标题
*/
private String title;
/**
* 工单描述
*/
private String description;
/**
* 优先级
*/
private Integer priority;
/**
* 工单状态
*/
private String status;
/**
* 区域ID
*/
private Long areaId;
/**
* 具体位置描述
*/
private String location;
/**
* 加急原因
*/
private String urgentReason;
/**
* 当前执行人ID
*/
private Long assigneeId;
/**
* 当前执行人姓名
*/
private String assigneeName;
/**
* 巡检员ID
*/
private Long inspectorId;
/**
* 巡检员姓名
*/
private String inspectorName;
/**
* 工单开始时间
*/
private LocalDateTime startTime;
/**
* 工单结束时间
*/
private LocalDateTime endTime;
/**
* 验收评分
*/
private Integer qualityScore;
/**
* 验收评语
*/
private String qualityComment;
/**
* 响应耗时(秒)
*/
private Integer responseSeconds;
/**
* 完成耗时(秒)
*/
private Integer completionSeconds;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 更新时间
*/
private LocalDateTime updateTime;
/**
* 触发来源
*/
private String triggerSource;
/**
* 触发规则ID
*/
private Long triggerRuleId;
/**
* 触发设备ID
*/
private Long triggerDeviceId;
/**
* 触发设备Key
*/
private String triggerDeviceKey;
/**
* 扩展信息Map形式通用字段
*/
@Builder.Default
private Map<String, Object> extInfo = new java.util.HashMap<>();
}

View File

@@ -0,0 +1,46 @@
package com.viewsh.module.ops.service;
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
import java.util.Map;
/**
* 工单扩展表查询处理器接口
* <p>
* 各业务模块实现此接口,提供对应业务类型的扩展信息加载能力。
* 例如:
* - environment-biz 实现 CleanOrderExtQueryHandler
* - security-biz 实现 SecurityOrderExtQueryHandler
*
* @author lzh
*/
public interface OrderExtQueryHandler {
/**
* 是否支持此业务类型
*
* @param orderType 工单类型CLEAN、SECURITY、FACILITIES
* @return true-支持false-不支持
*/
boolean supports(String orderType);
/**
* 为汇总VO填充扩展信息
* <p>
* 用于分页查询场景,将扩展信息填充到 extInfo Map 中
*
* @param vo 汇总VO
* @param orderId 工单ID
*/
void enrichWithExtInfo(OrderSummaryVO vo, Long orderId);
/**
* 构建详情VO
* <p>
* 用于详情查询场景返回包含完整扩展信息的详情VO
*
* @param order 工单主表数据
* @return 详情VO
*/
OrderDetailVO buildDetailVO(OpsOrderDO order);
}

View File

@@ -0,0 +1,220 @@
package com.viewsh.module.ops.service;
import com.viewsh.framework.common.pojo.PageResult;
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
import java.util.Map;
/**
* 工单中心查询服务接口
* <p>
* 职责:
* 1. 综合分页查询(支持所有业务类型)
* 2. 工单详情查询(含完整扩展信息)
* 3. 工单统计信息
*
* @author lzh
*/
public interface OrderQueryService {
/**
* 分页查询工单(支持所有业务类型)
* <p>
* 查询主表数据后,自动调用对应的扩展查询处理器填充扩展信息
*
* @param query 查询条件
* @return 分页结果
*/
PageResult<OrderSummaryVO> queryPage(OrderQuery query);
/**
* 查询工单详情(含完整扩展信息)
* <p>
* 根据工单的业务类型,调用对应的扩展查询处理器加载完整扩展信息
*
* @param orderId 工单ID
* @return 详情VO
*/
OrderDetailVO getDetail(Long orderId);
/**
* 获取工单统计信息
* <p>
* 按业务类型分组返回工单数量统计
*
* @param groupBy 分组维度status按状态统计null只按类型统计
* @return 统计结果
*/
Map<String, Object> getStats(String groupBy);
/**
* 工单查询条件
*/
class OrderQuery {
/**
* 页码从1开始
*/
private Integer page = 1;
/**
* 每页大小
*/
private Integer size = 20;
/**
* 工单类型(可选)
*/
private String orderType;
/**
* 工单状态(可选)
*/
private String status;
/**
* 优先级(可选)
*/
private Integer priority;
/**
* 区域ID可选
*/
private Long areaId;
/**
* 执行人ID可选
*/
private Long assigneeId;
/**
* 工单编号模糊查询(可选)
*/
private String orderCode;
/**
* 标题模糊查询(可选)
*/
private String title;
public Integer getPage() {
return page;
}
public void setPage(Integer page) {
this.page = page;
}
public Integer getSize() {
return size;
}
public void setSize(Integer size) {
this.size = size;
}
public String getOrderType() {
return orderType;
}
public void setOrderType(String orderType) {
this.orderType = orderType;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public Integer getPriority() {
return priority;
}
public void setPriority(Integer priority) {
this.priority = priority;
}
public Long getAreaId() {
return areaId;
}
public void setAreaId(Long areaId) {
this.areaId = areaId;
}
public Long getAssigneeId() {
return assigneeId;
}
public void setAssigneeId(Long assigneeId) {
this.assigneeId = assigneeId;
}
public String getOrderCode() {
return orderCode;
}
public void setOrderCode(String orderCode) {
this.orderCode = orderCode;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public static OrderQuery builder() {
return new OrderQuery();
}
public OrderQuery withPage(Integer page) {
this.page = page;
return this;
}
public OrderQuery withSize(Integer size) {
this.size = size;
return this;
}
public OrderQuery withOrderType(String orderType) {
this.orderType = orderType;
return this;
}
public OrderQuery withStatus(String status) {
this.status = status;
return this;
}
public OrderQuery withPriority(Integer priority) {
this.priority = priority;
return this;
}
public OrderQuery withAreaId(Long areaId) {
this.areaId = areaId;
return this;
}
public OrderQuery withAssigneeId(Long assigneeId) {
this.assigneeId = assigneeId;
return this;
}
public OrderQuery withOrderCode(String orderCode) {
this.orderCode = orderCode;
return this;
}
public OrderQuery withTitle(String title) {
this.title = title;
return this;
}
}
}

View File

@@ -0,0 +1,186 @@
package com.viewsh.module.ops.service;
import com.viewsh.framework.common.pojo.PageResult;
import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.viewsh.module.ops.dal.dataobject.workorder.OpsOrderDO;
import com.viewsh.module.ops.dal.mysql.workorder.OpsOrderMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 工单中心查询服务实现
*
* @author lzh
*/
@Slf4j
@Service
public class OrderQueryServiceImpl implements OrderQueryService {
@Resource
private OpsOrderMapper opsOrderMapper;
/**
* 扩展查询处理器列表(由各业务模块注入)
*/
private final List<OrderExtQueryHandler> extQueryHandlers;
public OrderQueryServiceImpl(List<OrderExtQueryHandler> extQueryHandlers) {
this.extQueryHandlers = extQueryHandlers;
log.info("OrderQueryService 初始化,注册 {} 个扩展查询处理器", extQueryHandlers.size());
}
@Override
public PageResult<OrderSummaryVO> queryPage(OrderQuery query) {
// 1. 构建查询条件
LambdaQueryWrapperX<OpsOrderDO> wrapper = new LambdaQueryWrapperX<OpsOrderDO>()
.eqIfPresent(OpsOrderDO::getOrderType, query.getOrderType())
.eqIfPresent(OpsOrderDO::getStatus, query.getStatus())
.eqIfPresent(OpsOrderDO::getPriority, query.getPriority())
.eqIfPresent(OpsOrderDO::getAreaId, query.getAreaId())
.eqIfPresent(OpsOrderDO::getAssigneeId, query.getAssigneeId())
.likeIfPresent(OpsOrderDO::getOrderCode, query.getOrderCode())
.likeIfPresent(OpsOrderDO::getTitle, query.getTitle())
.orderByDesc(OpsOrderDO::getCreateTime);
// 2. 分页查询主表
PageResult<OpsOrderDO> pageResult = opsOrderMapper.selectPage(query.getPage(), query.getSize(), wrapper);
// 3. <20><><EFBFBD>换为 VO 并填充扩展信息
List<OrderSummaryVO> voList = pageResult.getList().stream()
.map(this::convertToSummaryVO)
.toList();
return new PageResult<>(voList, pageResult.getTotal());
}
@Override
public OrderDetailVO getDetail(Long orderId) {
// 1. 查询主表
OpsOrderDO order = opsOrderMapper.selectById(orderId);
if (order == null) {
throw new RuntimeException("工单不存在: " + orderId);
}
// 2. 找到对应的扩展查询处理器
OrderExtQueryHandler handler = findExtQueryHandler(order.getOrderType());
// 3. 构建详情VO
if (handler != null) {
return handler.buildDetailVO(order);
} else {
// 没有对应的扩展查询处理器返回基础VO
return convertToDetailVO(order);
}
}
@Override
public Map<String, Object> getStats(String groupBy) {
Map<String, Object> result = new HashMap<>();
if ("status".equals(groupBy)) {
// 按类型和状态统计
List<OpsOrderDO> allOrders = opsOrderMapper.selectList();
Map<String, Map<String, Long>> typeStatusMap = new HashMap<>();
for (OpsOrderDO order : allOrders) {
String type = order.getOrderType();
String status = order.getStatus();
typeStatusMap.computeIfAbsent(type, k -> new HashMap<>())
.merge(status, 1L, Long::sum);
}
result.putAll(typeStatusMap);
} else {
// 只按类型统计
List<OpsOrderDO> allOrders = opsOrderMapper.selectList();
Map<String, Long> typeCountMap = new HashMap<>();
for (OpsOrderDO order : allOrders) {
String type = order.getOrderType();
typeCountMap.merge(type, 1L, Long::sum);
}
result.putAll(typeCountMap);
}
return result;
}
/**
* 转换为汇总VO并填充扩展信息
*/
private OrderSummaryVO convertToSummaryVO(OpsOrderDO order) {
OrderSummaryVO vo = OrderSummaryVO.builder()
.id(order.getId())
.orderCode(order.getOrderCode())
.orderType(order.getOrderType())
.title(order.getTitle())
.description(order.getDescription())
.priority(order.getPriority())
.status(order.getStatus())
.areaId(order.getAreaId())
.location(order.getLocation())
.assigneeId(order.getAssigneeId())
.startTime(order.getStartTime())
.endTime(order.getEndTime())
.createTime(order.getCreateTime())
.build();
// 填充扩展信息
OrderExtQueryHandler handler = findExtQueryHandler(order.getOrderType());
if (handler != null) {
handler.enrichWithExtInfo(vo, order.getId());
}
return vo;
}
/**
* 转换为详情VO基础版本不含扩展信息
*/
private OrderDetailVO convertToDetailVO(OpsOrderDO order) {
return OrderDetailVO.builder()
.id(order.getId())
.orderCode(order.getOrderCode())
.orderType(order.getOrderType())
.sourceType(order.getSourceType())
.title(order.getTitle())
.description(order.getDescription())
.priority(order.getPriority())
.status(order.getStatus())
.areaId(order.getAreaId())
.location(order.getLocation())
.urgentReason(order.getUrgentReason())
.assigneeId(order.getAssigneeId())
.inspectorId(order.getInspectorId())
.startTime(order.getStartTime())
.endTime(order.getEndTime())
.qualityScore(order.getQualityScore())
.qualityComment(order.getQualityComment())
.responseSeconds(order.getResponseSeconds())
.completionSeconds(order.getCompletionSeconds())
.createTime(order.getCreateTime())
.updateTime(order.getUpdateTime())
.triggerSource(order.getTriggerSource())
.triggerRuleId(order.getTriggerRuleId())
.triggerDeviceId(order.getTriggerDeviceId())
.triggerDeviceKey(order.getTriggerDeviceKey())
.build();
}
/**
* 查找对应的扩展查询处理器
*/
private OrderExtQueryHandler findExtQueryHandler(String orderType) {
return extQueryHandlers.stream()
.filter(handler -> handler.supports(orderType))
.findFirst()
.orElse(null);
}
}

View File

@@ -0,0 +1,103 @@
package com.viewsh.module.ops.service;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 工单汇总信息 VO
* <p>
* 用于工单中心分页查询包含主表信息和扩展信息Map形式
*
* @author lzh
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderSummaryVO {
/**
* 工单ID
*/
private Long id;
/**
* 工单编号
*/
private String orderCode;
/**
* 工单类型CLEAN/SECURITY/FACILITIES/SERVICE
*/
private String orderType;
/**
* 工单标题
*/
private String title;
/**
* 工单描述
*/
private String description;
/**
* 优先级0=P0/1=P1/2=P2
*/
private Integer priority;
/**
* 工单状态
*/
private String status;
/**
* 区域ID
*/
private Long areaId;
/**
* 具体位置描述
*/
private String location;
/**
* 当前执行人ID
*/
private Long assigneeId;
/**
* 当前执行人姓名(冗余)
*/
private String assigneeName;
/**
* 工单开始时间
*/
private LocalDateTime startTime;
/**
* 工单结束时间
*/
private LocalDateTime endTime;
/**
* 创建时间
*/
private LocalDateTime createTime;
/**
* 扩展信息Map形式
* <p>
* 根据工单类型不同,包含不同的扩展字段:
* - CLEAN: expectedDuration, cleaningType, difficultyLevel
* - SECURITY: route, checkpoint, patrolTime
*/
@Builder.Default
private Map<String, Object> extInfo = new java.util.HashMap<>();
}