|
|
@@ -1,4 +1,4 @@
|
|
|
-package com.zksy.base.service.impl;
|
|
|
+package com.zksy.ops.service.impl;
|
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
|
@@ -6,22 +6,21 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
|
import com.zksy.base.domain.EquipmentBase;
|
|
|
import com.zksy.base.domain.HazardHiddenAccount;
|
|
|
-import com.zksy.base.domain.WorkOrder;
|
|
|
-import com.zksy.base.domain.WorkOrderLog;
|
|
|
-import com.zksy.base.domain.vo.WorkOrderPushVO;
|
|
|
-import com.zksy.base.enums.WorkOrderDelayApplyStatusEnum;
|
|
|
-import com.zksy.base.enums.WorkOrderLevelEnum;
|
|
|
-import com.zksy.base.enums.WorkOrderLogOperTypeEnum;
|
|
|
-import com.zksy.base.enums.WorkOrderStatusEnum;
|
|
|
+import com.zksy.ops.domain.WorkOrder;
|
|
|
+import com.zksy.ops.domain.WorkOrderLog;
|
|
|
+import com.zksy.ops.domain.vo.WorkOrderPushVO;
|
|
|
+import com.zksy.ops.enums.WorkOrderDelayApplyStatusEnum;
|
|
|
+import com.zksy.ops.enums.WorkOrderLevelEnum;
|
|
|
+import com.zksy.ops.enums.WorkOrderLogOperTypeEnum;
|
|
|
+import com.zksy.ops.enums.WorkOrderStatusEnum;
|
|
|
import com.zksy.base.mapper.EquipmentBaseMapper;
|
|
|
import com.zksy.base.mapper.HazardHiddenAccountMapper;
|
|
|
-import com.zksy.base.mapper.WorkOrderLogMapper;
|
|
|
-import com.zksy.base.mapper.WorkOrderMapper;
|
|
|
-import com.zksy.base.service.BusinessWorkOrderService;
|
|
|
+import com.zksy.ops.mapper.WorkOrderLogMapper;
|
|
|
+import com.zksy.ops.mapper.WorkOrderMapper;
|
|
|
+import com.zksy.common.constant.UserConstants;
|
|
|
import com.zksy.common.core.domain.TreeSelect;
|
|
|
import com.zksy.common.core.domain.entity.SysUser;
|
|
|
import com.zksy.common.core.domain.model.LoginUser;
|
|
|
-import com.zksy.common.constant.UserConstants;
|
|
|
import com.zksy.common.exception.ServiceException;
|
|
|
import com.zksy.common.utils.DateUtils;
|
|
|
import com.zksy.common.utils.SecurityUtils;
|
|
|
@@ -32,7 +31,9 @@ import com.zksy.ops.dto.WorkOrderDetailVO;
|
|
|
import com.zksy.ops.dto.WorkOrderDispatchDTO;
|
|
|
import com.zksy.ops.dto.WorkOrderOperateDTO;
|
|
|
import com.zksy.ops.dto.WorkOrderPageDTO;
|
|
|
+import com.zksy.ops.dto.WorkOrderStatusStatVO;
|
|
|
import com.zksy.ops.dto.WorkOrderVerifyDTO;
|
|
|
+import com.zksy.ops.service.WorkOrderService;
|
|
|
import com.zksy.ops.support.OpsAssignScopeSupport;
|
|
|
import com.zksy.system.service.ISysUserService;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
@@ -54,6 +55,7 @@ import java.util.Map;
|
|
|
import java.util.Objects;
|
|
|
import java.util.Set;
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
+import java.util.function.Consumer;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
@@ -62,11 +64,14 @@ import java.util.stream.Collectors;
|
|
|
* <p>状态流转:0待派单 →(派单) 1待接单 →(核查) 2已接单 →(处置) 3处理中
|
|
|
* →(提交验收) 4待验收 →(验收通过) 5已结案;延期审核通过后进入 6延期中;任意前置状态可 7已取消。</p>
|
|
|
*
|
|
|
- * @author zksy
|
|
|
+ * <p>实现约定:所有状态写入统一走 {@link #transition} / {@link #updateWrapper},
|
|
|
+ * 由 UPDATE 的 WHERE 条件兜住并发(乐观推进),避免「先查再改」产生竞态。</p>
|
|
|
+ *
|
|
|
+ * @author zengy
|
|
|
*/
|
|
|
@Slf4j
|
|
|
@Service
|
|
|
-public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder> implements BusinessWorkOrderService {
|
|
|
+public class WorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder> implements WorkOrderService {
|
|
|
|
|
|
/**
|
|
|
* 工单编号前缀
|
|
|
@@ -83,6 +88,11 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
*/
|
|
|
private static final int ORDER_NO_RANDOM_BOUND = 10000;
|
|
|
|
|
|
+ /**
|
|
|
+ * 分页默认每页条数
|
|
|
+ */
|
|
|
+ private static final long DEFAULT_PAGE_SIZE = 10L;
|
|
|
+
|
|
|
/**
|
|
|
* 框架数据范围参数名,与 DataScopeAspect.DATA_SCOPE 保持一致。
|
|
|
* zksy-system 模块不依赖 zksy-framework(DataScopeAspect 所在模块),故此处使用字面量。
|
|
|
@@ -105,84 +115,119 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
private OpsAssignScopeSupport assignScopeSupport;
|
|
|
|
|
|
@Override
|
|
|
- public Page<WorkOrder> findByPage(WorkOrderPageDTO dto) {
|
|
|
- if (dto == null) {
|
|
|
- dto = new WorkOrderPageDTO();
|
|
|
- }
|
|
|
- long pageNum = (dto.getPageNum() == null || dto.getPageNum() < 1) ? 1L : dto.getPageNum();
|
|
|
- long pageSize = (dto.getPageSize() == null || dto.getPageSize() < 1) ? 10L : dto.getPageSize();
|
|
|
- Page<WorkOrder> page = new Page<>(pageNum, pageSize);
|
|
|
+ public Page<WorkOrder> findByPage(WorkOrderPageDTO query) {
|
|
|
+ WorkOrderPageDTO condition = query == null ? new WorkOrderPageDTO() : query;
|
|
|
+ long pageNum = condition.getPageNum() == null || condition.getPageNum() < 1 ? 1L : condition.getPageNum();
|
|
|
+ long pageSize = condition.getPageSize() == null || condition.getPageSize() < 1 ? DEFAULT_PAGE_SIZE : condition.getPageSize();
|
|
|
|
|
|
if (assignScopeSupport.currentUserId() == null) {
|
|
|
// 未获取到登录用户时返回空结果,避免越权查看
|
|
|
return new Page<>(pageNum, pageSize);
|
|
|
}
|
|
|
|
|
|
- LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
|
|
|
- String dataScope = dataScopeSql(dto);
|
|
|
- if (StringUtils.isNotEmpty(dataScope)) {
|
|
|
- wrapper.apply(dataScope);
|
|
|
+ Page<WorkOrder> result = this.page(new Page<>(pageNum, pageSize), buildQueryWrapper(condition));
|
|
|
+ fillUserName(result.getRecords());
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public WorkOrderStatusStatVO countByStatus(WorkOrderPageDTO query) {
|
|
|
+ WorkOrderPageDTO condition = query == null ? new WorkOrderPageDTO() : query;
|
|
|
+ WorkOrderStatusStatVO stat = new WorkOrderStatusStatVO();
|
|
|
+ // 先把全部合法状态补 0,前端筛选条不依赖「缺失即 0」的兜底
|
|
|
+ for (WorkOrderStatusEnum status : WorkOrderStatusEnum.values()) {
|
|
|
+ stat.addCount(status.getCode(), 0L);
|
|
|
}
|
|
|
- wrapper.eq(dto.getOrderLevel() != null, WorkOrder::getOrderLevel, dto.getOrderLevel()).eq(dto.getDelayApplyStatus() != null, WorkOrder::getDelayApplyStatus, dto.getDelayApplyStatus()).eq(StringUtils.isNotEmpty(dto.getDeviceCode()), WorkOrder::getDeviceCode, dto.getDeviceCode()).ge(StringUtils.isNotEmpty(dto.getFaultTimeStart()), WorkOrder::getFaultTime, DateUtils.parseDate(dto.getFaultTimeStart())).le(StringUtils.isNotEmpty(dto.getFaultTimeEnd()), WorkOrder::getFaultTime, DateUtils.parseDate(dto.getFaultTimeEnd()));
|
|
|
- if (StringUtils.isNotEmpty(dto.getOrderStatus())) {
|
|
|
- List<Integer> statusList = parseStatusList(dto.getOrderStatus());
|
|
|
- if (!statusList.isEmpty()) {
|
|
|
- wrapper.in(WorkOrder::getOrderStatus, statusList);
|
|
|
+
|
|
|
+ if (assignScopeSupport.currentUserId() == null) {
|
|
|
+ // 与 findByPage 保持一致:未获取到登录用户时返回全 0,避免越权查看数据量
|
|
|
+ return stat;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 不叠加状态筛选,否则点击某个状态后其它状态的计数会全部变成 0
|
|
|
+ List<Map<String, Object>> rows = baseMapper.countByStatus(buildFilterWrapper(condition, false));
|
|
|
+ long total = 0L;
|
|
|
+ if (!CollectionUtils.isEmpty(rows)) {
|
|
|
+ for (Map<String, Object> row : rows) {
|
|
|
+ Long count = toLong(row.get("cnt"));
|
|
|
+ if (count == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ Long statusCode = toLong(row.get("status"));
|
|
|
+ stat.addCount(statusCode == null ? null : statusCode.intValue(), count);
|
|
|
+ total += count;
|
|
|
}
|
|
|
}
|
|
|
- if (StringUtils.isNotEmpty(dto.getKeyword())) {
|
|
|
- String keyword = dto.getKeyword().trim();
|
|
|
- wrapper.and(w -> w.like(WorkOrder::getOrderNo, keyword).or().like(WorkOrder::getDeviceCode, keyword).or().like(WorkOrder::getOrderDesc, keyword));
|
|
|
- }
|
|
|
- applyDeviceTypeScope(wrapper, dto.getDeviceTypeParentId());
|
|
|
- wrapper.orderByAsc(WorkOrder::getOrderLevel).orderByAsc(WorkOrder::getOrderStatus).orderByDesc(WorkOrder::getFaultTime).orderByDesc(WorkOrder::getCreateTime);
|
|
|
-
|
|
|
- Page<WorkOrder> result = this.page(page, wrapper);
|
|
|
- fillUserName(result.getRecords());
|
|
|
- return result;
|
|
|
+ stat.setTotal(total);
|
|
|
+ return stat;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public WorkOrderDetailVO getDetailById(Long orderId) {
|
|
|
+ public WorkOrderDetailVO getDetail(Long orderId) {
|
|
|
WorkOrder order = getOrderById(orderId);
|
|
|
fillUserName(Collections.singletonList(order));
|
|
|
- WorkOrderDetailVO vo = new WorkOrderDetailVO();
|
|
|
- vo.setOrder(order);
|
|
|
- vo.setLogs(getWorkOrderLogs(orderId));
|
|
|
- return vo;
|
|
|
+
|
|
|
+ WorkOrderDetailVO detail = new WorkOrderDetailVO();
|
|
|
+ detail.setOrder(order);
|
|
|
+ detail.setLogs(listLogs(orderId));
|
|
|
+ return detail;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- public List<WorkOrderLog> getWorkOrderLogs(Long orderId) {
|
|
|
+ public List<WorkOrderLog> listLogs(Long orderId) {
|
|
|
if (orderId == null) {
|
|
|
return Collections.emptyList();
|
|
|
}
|
|
|
List<WorkOrderLog> logs = workOrderLogMapper.selectList(new LambdaQueryWrapper<WorkOrderLog>().eq(WorkOrderLog::getOrderId, orderId).orderByDesc(WorkOrderLog::getOperTime));
|
|
|
- if (!CollectionUtils.isEmpty(logs)) {
|
|
|
- for (WorkOrderLog item : logs) {
|
|
|
- item.setOperTypeDesc(WorkOrderLogOperTypeEnum.getDescByCode(item.getOperType()));
|
|
|
- item.setAttachList(splitAttach(item.getAttachUrl()));
|
|
|
- }
|
|
|
+ for (WorkOrderLog item : logs) {
|
|
|
+ item.setOperTypeDesc(WorkOrderLogOperTypeEnum.getDescByCode(item.getOperType()));
|
|
|
+ item.setAttachList(splitAttach(item.getAttachUrl()));
|
|
|
}
|
|
|
return logs;
|
|
|
}
|
|
|
|
|
|
- // ==================== 新增 ====================
|
|
|
+ @Override
|
|
|
+ public List<TreeSelect> assignableDeptTree() {
|
|
|
+ return assignScopeSupport.assignableDeptTree();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<SysUser> assignableUsers(Long deptId) {
|
|
|
+ return assignScopeSupport.assignableUsers(deptId);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<WorkOrderPushVO> listPendingOrOverdue(Date now) {
|
|
|
+ Date currentTime = now == null ? new Date() : now;
|
|
|
+ List<WorkOrder> orders = baseMapper.selectPendingOrOverdue(currentTime);
|
|
|
+ if (CollectionUtils.isEmpty(orders)) {
|
|
|
+ return new ArrayList<>();
|
|
|
+ }
|
|
|
+ List<WorkOrderPushVO> result = new ArrayList<>(orders.size());
|
|
|
+ for (WorkOrder order : orders) {
|
|
|
+ WorkOrderPushVO item = toPushVO(order, currentTime);
|
|
|
+ if (item != null) {
|
|
|
+ result.add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public Long addWorkOrder(WorkOrder workOrder) {
|
|
|
+ public Long createWorkOrder(WorkOrder workOrder) {
|
|
|
if (workOrder == null || StringUtils.isEmpty(workOrder.getOrderDesc())) {
|
|
|
throw new ServiceException("故障描述不能为空");
|
|
|
}
|
|
|
checkDeviceOrderConflict(workOrder.getDeviceId());
|
|
|
+
|
|
|
+ Date now = new Date();
|
|
|
workOrder.setOrderId(null);
|
|
|
workOrder.setOrderNo(generateOrderNo());
|
|
|
workOrder.setOrderLevel(WorkOrderLevelEnum.getByCode(workOrder.getOrderLevel()).getCode());
|
|
|
if (StringUtils.isNotEmpty(workOrder.getDeviceId()) && StringUtils.isEmpty(workOrder.getDeviceCode())) {
|
|
|
workOrder.setDeviceCode(resolveDeviceCode(workOrder.getDeviceId()));
|
|
|
}
|
|
|
- Date now = new Date();
|
|
|
if (workOrder.getFaultTime() == null) {
|
|
|
workOrder.setFaultTime(now);
|
|
|
}
|
|
|
@@ -209,6 +254,7 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
}
|
|
|
checkDeviceOrderConflict(equipmentId);
|
|
|
|
|
|
+ Date now = new Date();
|
|
|
WorkOrder workOrder = new WorkOrder();
|
|
|
workOrder.setOrderNo(generateOrderNo());
|
|
|
workOrder.setHazardId(hazardId);
|
|
|
@@ -225,7 +271,6 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
if (StringUtils.isNotEmpty(hazardImages)) {
|
|
|
workOrder.setMaintenanceBeforeImg(hazardImages);
|
|
|
}
|
|
|
- Date now = new Date();
|
|
|
workOrder.setFaultTime(now);
|
|
|
workOrder.setCreateTime(now);
|
|
|
workOrder.setUpdateTime(now);
|
|
|
@@ -236,17 +281,15 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
return workOrder.getOrderId();
|
|
|
}
|
|
|
|
|
|
- // ==================== 流程操作 ====================
|
|
|
-
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public boolean dispatch(WorkOrderDispatchDTO dto) {
|
|
|
- Long orderId = requireOrderId(dto == null ? null : dto.getOrderId());
|
|
|
+ public boolean dispatch(Long orderId, WorkOrderDispatchDTO dto) {
|
|
|
+ Long id = requireOrderId(orderId);
|
|
|
Long handlerId = dto.getHandlerId();
|
|
|
if (handlerId == null) {
|
|
|
throw new ServiceException("责任人不能为空");
|
|
|
}
|
|
|
- WorkOrder order = getOrderById(orderId);
|
|
|
+ WorkOrder order = getOrderById(id);
|
|
|
if (!Objects.equals(WorkOrderStatusEnum.PENDING_DISPATCH.getCode(), order.getOrderStatus())) {
|
|
|
throw new ServiceException("仅待派单工单允许派单");
|
|
|
}
|
|
|
@@ -256,137 +299,127 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
throw new ServiceException("责任人不存在或已停用");
|
|
|
}
|
|
|
|
|
|
- Long operUserId = currentUserId();
|
|
|
Date now = new Date();
|
|
|
- this.update(new LambdaUpdateWrapper<WorkOrder>().set(WorkOrder::getOrderStatus, WorkOrderStatusEnum.PENDING_ACCEPT.getCode()).set(WorkOrder::getReceiveUser, handlerId).set(WorkOrder::getDispatchUser, operUserId).set(WorkOrder::getDispatchTime, now)
|
|
|
+ this.update(updateWrapper(id).set(WorkOrder::getOrderStatus, WorkOrderStatusEnum.PENDING_ACCEPT.getCode()).set(WorkOrder::getReceiveUser, handlerId).set(WorkOrder::getDispatchUser, currentUserId()).set(WorkOrder::getDispatchTime, now)
|
|
|
// 数据归属部门跟随派单指定的责任人
|
|
|
- .set(handler.getDeptId() != null, WorkOrder::getDeptId, handler.getDeptId()).set(WorkOrder::getUpdateTime, now).eq(WorkOrder::getOrderId, orderId).eq(WorkOrder::getOrderStatus, WorkOrderStatusEnum.PENDING_DISPATCH.getCode()));
|
|
|
+ .set(handler.getDeptId() != null, WorkOrder::getDeptId, handler.getDeptId()).eq(WorkOrder::getOrderStatus, WorkOrderStatusEnum.PENDING_DISPATCH.getCode()));
|
|
|
|
|
|
String logContent = "派单给责任人:" + userLabel(handler) + (StringUtils.isEmpty(dto.getRemarks()) ? "" : ",备注:" + dto.getRemarks());
|
|
|
- writeLog(orderId, WorkOrderLogOperTypeEnum.DISPATCH, logContent, null);
|
|
|
+ writeLog(id, WorkOrderLogOperTypeEnum.DISPATCH, logContent, null);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public boolean accept(WorkOrderOperateDTO dto) {
|
|
|
- WorkOrder order = loadForOperate(dto, "核查接单");
|
|
|
- if (!Objects.equals(WorkOrderStatusEnum.PENDING_ACCEPT.getCode(), order.getOrderStatus())) {
|
|
|
- throw new ServiceException("仅待接单工单允许核查接单");
|
|
|
- }
|
|
|
+ public boolean accept(Long orderId, WorkOrderOperateDTO dto) {
|
|
|
+ WorkOrder order = getOrderById(orderId);
|
|
|
+ requireResponsible(order, currentUserId(), "核查接单");
|
|
|
+ requireStatus(order, WorkOrderStatusEnum.PENDING_ACCEPT, "仅待接单工单允许核查接单");
|
|
|
// 核查接单必须上传现场情况附件
|
|
|
- if (StringUtils.isEmpty(attach(dto))) {
|
|
|
+ if (StringUtils.isEmpty(dto.getAttachUrl())) {
|
|
|
throw new ServiceException("核查接单必须上传现场情况附件");
|
|
|
}
|
|
|
- Date now = new Date();
|
|
|
- LambdaUpdateWrapper<WorkOrder> uw = new LambdaUpdateWrapper<WorkOrder>().set(WorkOrder::getOrderStatus, WorkOrderStatusEnum.ACCEPTED.getCode())
|
|
|
- // 维修前图片固定来源于核查接单上传的现场情况附件
|
|
|
- .set(WorkOrder::getMaintenanceBeforeImg, attach(dto)).set(WorkOrder::getUpdateTime, now).eq(WorkOrder::getOrderId, order.getOrderId()).eq(WorkOrder::getOrderStatus, WorkOrderStatusEnum.PENDING_ACCEPT.getCode());
|
|
|
- this.update(uw);
|
|
|
|
|
|
- writeLog(order.getOrderId(), WorkOrderLogOperTypeEnum.ACCEPT, buildDesc("", content(dto)), attach(dto));
|
|
|
- return true;
|
|
|
+ Long id = order.getOrderId();
|
|
|
+ // 维修前图片固定来源于核查接单上传的现场情况附件
|
|
|
+ boolean updated = transition(id, WorkOrderStatusEnum.PENDING_ACCEPT.getCode(), WorkOrderStatusEnum.ACCEPTED.getCode(), wrapper -> wrapper.set(WorkOrder::getMaintenanceBeforeImg, dto.getAttachUrl()));
|
|
|
+ writeLog(id, WorkOrderLogOperTypeEnum.ACCEPT, buildDesc("", dto.getContent()), dto.getAttachUrl());
|
|
|
+ return updated;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public boolean process(WorkOrderOperateDTO dto) {
|
|
|
- WorkOrder order = loadForOperate(dto, "处置");
|
|
|
+ public boolean process(Long orderId, WorkOrderOperateDTO dto) {
|
|
|
+ WorkOrder order = getOrderById(orderId);
|
|
|
+ requireResponsible(order, currentUserId(), "处置");
|
|
|
Integer status = order.getOrderStatus();
|
|
|
if (!WorkOrderStatusEnum.canProcess(status)) {
|
|
|
throw new ServiceException("当前工单状态不允许处置");
|
|
|
}
|
|
|
- Date now = new Date();
|
|
|
+
|
|
|
// 已接单 → 处理中;处理中/延期中 保持原状态,支持多次处置
|
|
|
Integer targetStatus = Objects.equals(WorkOrderStatusEnum.ACCEPTED.getCode(), status) ? WorkOrderStatusEnum.PROCESSING.getCode() : status;
|
|
|
- LambdaUpdateWrapper<WorkOrder> uw = new LambdaUpdateWrapper<WorkOrder>().set(WorkOrder::getOrderStatus, targetStatus).set(WorkOrder::getUpdateTime, now).eq(WorkOrder::getOrderId, order.getOrderId()).eq(WorkOrder::getOrderStatus, status);
|
|
|
- this.update(uw);
|
|
|
+ boolean updated = transition(order.getOrderId(), status, targetStatus, null);
|
|
|
|
|
|
- syncHazardStatus(order, "整改中", content(dto));
|
|
|
- writeLog(order.getOrderId(), WorkOrderLogOperTypeEnum.PROCESS, buildDesc("", content(dto)), attach(dto));
|
|
|
- return true;
|
|
|
+ syncHazardStatus(order, "整改中", dto.getContent());
|
|
|
+ writeLog(order.getOrderId(), WorkOrderLogOperTypeEnum.PROCESS, buildDesc("", dto.getContent()), dto.getAttachUrl());
|
|
|
+ return updated;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public boolean submitVerify(WorkOrderOperateDTO dto) {
|
|
|
- WorkOrder order = loadForOperate(dto, "提交验收");
|
|
|
+ public boolean submitVerify(Long orderId, WorkOrderOperateDTO dto) {
|
|
|
+ WorkOrder order = getOrderById(orderId);
|
|
|
+ requireResponsible(order, currentUserId(), "提交验收");
|
|
|
Integer status = order.getOrderStatus();
|
|
|
if (!WorkOrderStatusEnum.canSubmitVerify(status)) {
|
|
|
throw new ServiceException("仅处理中/延期中工单允许提交验收");
|
|
|
}
|
|
|
// 提交验收必须上传维修后的情况附件
|
|
|
- if (StringUtils.isEmpty(attach(dto))) {
|
|
|
+ if (StringUtils.isEmpty(dto.getAttachUrl())) {
|
|
|
throw new ServiceException("提交验收必须上传维修后的情况附件");
|
|
|
}
|
|
|
- Date now = new Date();
|
|
|
- // 提交验收:附件已在上方强制必填,直接写入工单维修后图片
|
|
|
- LambdaUpdateWrapper<WorkOrder> uw = new LambdaUpdateWrapper<WorkOrder>().set(WorkOrder::getOrderStatus, WorkOrderStatusEnum.PENDING_VERIFY.getCode()).set(WorkOrder::getMaintenanceAfterImg, attach(dto)).set(WorkOrder::getUpdateTime, now).eq(WorkOrder::getOrderId, order.getOrderId()).eq(WorkOrder::getOrderStatus, status);
|
|
|
- this.update(uw);
|
|
|
|
|
|
- writeLog(order.getOrderId(), WorkOrderLogOperTypeEnum.SUBMIT_VERIFY, buildDesc("", content(dto)), attach(dto));
|
|
|
- return true;
|
|
|
+ // 附件已在上方强制必填,直接写入工单维修后图片
|
|
|
+ boolean updated = transition(order.getOrderId(), status, WorkOrderStatusEnum.PENDING_VERIFY.getCode(), wrapper -> wrapper.set(WorkOrder::getMaintenanceAfterImg, dto.getAttachUrl()));
|
|
|
+ writeLog(order.getOrderId(), WorkOrderLogOperTypeEnum.SUBMIT_VERIFY, buildDesc("", dto.getContent()), dto.getAttachUrl());
|
|
|
+ return updated;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public boolean verify(WorkOrderVerifyDTO dto) {
|
|
|
- Long orderId = requireOrderId(dto == null ? null : dto.getOrderId());
|
|
|
- Boolean passed = dto == null ? null : dto.getPassed();
|
|
|
+ public boolean verify(Long orderId, WorkOrderVerifyDTO dto) {
|
|
|
+ Long id = requireOrderId(orderId);
|
|
|
+ Boolean passed = dto.getPassed();
|
|
|
if (passed == null) {
|
|
|
throw new ServiceException("验收结果不能为空");
|
|
|
}
|
|
|
- WorkOrder order = getOrderById(orderId);
|
|
|
- if (!Objects.equals(WorkOrderStatusEnum.PENDING_VERIFY.getCode(), order.getOrderStatus())) {
|
|
|
- throw new ServiceException("仅待验收工单允许验收");
|
|
|
- }
|
|
|
- Long operUserId = currentUserId();
|
|
|
+ WorkOrder order = getOrderById(id);
|
|
|
+ requireStatus(order, WorkOrderStatusEnum.PENDING_VERIFY, "仅待验收工单允许验收");
|
|
|
+
|
|
|
Date now = new Date();
|
|
|
WorkOrderStatusEnum targetStatus = passed ? WorkOrderStatusEnum.CLOSED : WorkOrderStatusEnum.PROCESSING;
|
|
|
- LambdaUpdateWrapper<WorkOrder> uw = new LambdaUpdateWrapper<WorkOrder>().set(WorkOrder::getOrderStatus, targetStatus.getCode()).set(WorkOrder::getVerifyUser, operUserId).set(WorkOrder::getVerifyTime, now).set(WorkOrder::getUpdateTime, now).eq(WorkOrder::getOrderId, orderId).eq(WorkOrder::getOrderStatus, WorkOrderStatusEnum.PENDING_VERIFY.getCode());
|
|
|
- if (passed) {
|
|
|
- uw.set(WorkOrder::getRealFinishTime, now);
|
|
|
- }
|
|
|
- if (StringUtils.isNotEmpty(dto.getContent())) {
|
|
|
- uw.set(WorkOrder::getRepairResult, dto.getContent());
|
|
|
- }
|
|
|
- this.update(uw);
|
|
|
+ boolean updated = transition(id, WorkOrderStatusEnum.PENDING_VERIFY.getCode(), targetStatus.getCode(), wrapper -> {
|
|
|
+ wrapper.set(WorkOrder::getVerifyUser, currentUserId()).set(WorkOrder::getVerifyTime, now);
|
|
|
+ if (passed) {
|
|
|
+ wrapper.set(WorkOrder::getRealFinishTime, now);
|
|
|
+ }
|
|
|
+ if (StringUtils.isNotEmpty(dto.getContent())) {
|
|
|
+ wrapper.set(WorkOrder::getRepairResult, dto.getContent());
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
if (passed) {
|
|
|
syncHazardStatus(order, "已整改", dto.getContent());
|
|
|
}
|
|
|
- writeLog(orderId, passed ? WorkOrderLogOperTypeEnum.VERIFY_PASS : WorkOrderLogOperTypeEnum.VERIFY_REJECT, buildDesc("验收意见", dto.getContent()), dto.getAttachUrl());
|
|
|
- return true;
|
|
|
+ writeLog(id, passed ? WorkOrderLogOperTypeEnum.VERIFY_PASS : WorkOrderLogOperTypeEnum.VERIFY_REJECT, buildDesc("验收意见", dto.getContent()), dto.getAttachUrl());
|
|
|
+ return updated;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public boolean cancel(WorkOrderOperateDTO dto) {
|
|
|
- Long orderId = requireOrderId(dto == null ? null : dto.getOrderId());
|
|
|
- WorkOrder order = getOrderById(orderId);
|
|
|
+ public boolean cancel(Long orderId, WorkOrderOperateDTO dto) {
|
|
|
+ Long id = requireOrderId(orderId);
|
|
|
+ WorkOrder order = getOrderById(id);
|
|
|
if (!WorkOrderStatusEnum.canCancel(order.getOrderStatus())) {
|
|
|
throw new ServiceException("当前工单状态不允许取消");
|
|
|
}
|
|
|
- if (StringUtils.isEmpty(content(dto))) {
|
|
|
+ if (StringUtils.isEmpty(dto.getContent())) {
|
|
|
throw new ServiceException("请填写取消原因");
|
|
|
}
|
|
|
- Date now = new Date();
|
|
|
- this.update(new LambdaUpdateWrapper<WorkOrder>().set(WorkOrder::getOrderStatus, WorkOrderStatusEnum.CANCELLED.getCode()).set(WorkOrder::getUpdateTime, now).eq(WorkOrder::getOrderId, orderId).eq(WorkOrder::getOrderStatus, order.getOrderStatus()));
|
|
|
|
|
|
- writeLog(orderId, WorkOrderLogOperTypeEnum.CANCEL, buildDesc("原因", content(dto)), attach(dto));
|
|
|
- return true;
|
|
|
+ boolean updated = transition(id, order.getOrderStatus(), WorkOrderStatusEnum.CANCELLED.getCode(), null);
|
|
|
+ writeLog(id, WorkOrderLogOperTypeEnum.CANCEL, buildDesc("原因", dto.getContent()), dto.getAttachUrl());
|
|
|
+ return updated;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public boolean applyDelay(WorkOrderDelayApplyDTO dto) {
|
|
|
- Long orderId = requireOrderId(dto == null ? null : dto.getOrderId());
|
|
|
- WorkOrder order = getOrderById(orderId);
|
|
|
- Long currentUserId = currentUserId();
|
|
|
- requireResponsible(order, currentUserId, "申请延期");
|
|
|
- if (!Objects.equals(WorkOrderStatusEnum.PROCESSING.getCode(), order.getOrderStatus())) {
|
|
|
- throw new ServiceException("仅处理中工单允许申请延期");
|
|
|
- }
|
|
|
+ public boolean applyDelay(Long orderId, WorkOrderDelayApplyDTO dto) {
|
|
|
+ Long id = requireOrderId(orderId);
|
|
|
+ WorkOrder order = getOrderById(id);
|
|
|
+ requireResponsible(order, currentUserId(), "申请延期");
|
|
|
+ requireStatus(order, WorkOrderStatusEnum.PROCESSING, "仅处理中工单允许申请延期");
|
|
|
if (!Objects.equals(WorkOrderDelayApplyStatusEnum.NONE.getCode(), order.getDelayApplyStatus())) {
|
|
|
throw new ServiceException("该工单已提交过延期申请,请勿重复提交");
|
|
|
}
|
|
|
@@ -398,47 +431,111 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
throw new ServiceException("延期截止时间格式不正确");
|
|
|
}
|
|
|
|
|
|
- Date now = new Date();
|
|
|
- this.update(new LambdaUpdateWrapper<WorkOrder>().set(WorkOrder::getDelayApplyStatus, WorkOrderDelayApplyStatusEnum.PENDING_AUDIT.getCode()).set(WorkOrder::getDelayedDeadline, delayedDeadline).set(WorkOrder::getDelayedReason, dto.getDelayedReason()).set(WorkOrder::getUpdateTime, now).eq(WorkOrder::getOrderId, orderId).eq(WorkOrder::getOrderStatus, WorkOrderStatusEnum.PROCESSING.getCode()).eq(WorkOrder::getDelayApplyStatus, WorkOrderDelayApplyStatusEnum.NONE.getCode()));
|
|
|
+ this.update(updateWrapper(id).set(WorkOrder::getDelayApplyStatus, WorkOrderDelayApplyStatusEnum.PENDING_AUDIT.getCode()).set(WorkOrder::getDelayedDeadline, delayedDeadline).set(WorkOrder::getDelayedReason, dto.getDelayedReason()).eq(WorkOrder::getOrderStatus, WorkOrderStatusEnum.PROCESSING.getCode()).eq(WorkOrder::getDelayApplyStatus, WorkOrderDelayApplyStatusEnum.NONE.getCode()));
|
|
|
|
|
|
- writeLog(orderId, WorkOrderLogOperTypeEnum.DELAY_APPLY, "申请延期至 " + dto.getDelayedDeadline() + ",原因:" + dto.getDelayedReason(), dto.getAttachUrl());
|
|
|
+ writeLog(id, WorkOrderLogOperTypeEnum.DELAY_APPLY, "申请延期至 " + dto.getDelayedDeadline() + ",原因:" + dto.getDelayedReason(), dto.getAttachUrl());
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public boolean auditDelay(WorkOrderDelayAuditDTO dto) {
|
|
|
- Long orderId = requireOrderId(dto == null ? null : dto.getOrderId());
|
|
|
+ public boolean auditDelay(Long orderId, WorkOrderDelayAuditDTO dto) {
|
|
|
+ Long id = requireOrderId(orderId);
|
|
|
Boolean approved = dto.getApproved();
|
|
|
if (approved == null) {
|
|
|
throw new ServiceException("延期审核结果不能为空");
|
|
|
}
|
|
|
- WorkOrder order = getOrderById(orderId);
|
|
|
+ WorkOrder order = getOrderById(id);
|
|
|
if (!Objects.equals(WorkOrderDelayApplyStatusEnum.PENDING_AUDIT.getCode(), order.getDelayApplyStatus())) {
|
|
|
throw new ServiceException("该工单没有待审核的延期申请");
|
|
|
}
|
|
|
+
|
|
|
WorkOrderDelayApplyStatusEnum applyStatus = approved ? WorkOrderDelayApplyStatusEnum.APPROVED : WorkOrderDelayApplyStatusEnum.REJECTED;
|
|
|
Integer orderStatus = approved ? WorkOrderStatusEnum.DELAYING.getCode() : order.getOrderStatus();
|
|
|
- Date now = new Date();
|
|
|
- this.update(new LambdaUpdateWrapper<WorkOrder>().set(WorkOrder::getDelayApplyStatus, applyStatus.getCode()).set(WorkOrder::getOrderStatus, orderStatus).set(WorkOrder::getUpdateTime, now).eq(WorkOrder::getOrderId, orderId).eq(WorkOrder::getDelayApplyStatus, WorkOrderDelayApplyStatusEnum.PENDING_AUDIT.getCode()));
|
|
|
+ this.update(updateWrapper(id).set(WorkOrder::getDelayApplyStatus, applyStatus.getCode()).set(WorkOrder::getOrderStatus, orderStatus).eq(WorkOrder::getDelayApplyStatus, WorkOrderDelayApplyStatusEnum.PENDING_AUDIT.getCode()));
|
|
|
|
|
|
- writeLog(orderId, approved ? WorkOrderLogOperTypeEnum.DELAY_APPROVE : WorkOrderLogOperTypeEnum.DELAY_REJECT, buildDesc("审核意见", dto.getAuditOpinion()), null);
|
|
|
+ writeLog(id, approved ? WorkOrderLogOperTypeEnum.DELAY_APPROVE : WorkOrderLogOperTypeEnum.DELAY_REJECT, buildDesc("审核意见", dto.getAuditOpinion()), null);
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
@Transactional(rollbackFor = Exception.class)
|
|
|
- public boolean deleteWithLogs(Long orderId) {
|
|
|
- if (orderId == null) {
|
|
|
- return false;
|
|
|
+ public boolean deleteWorkOrder(Long orderId) {
|
|
|
+ Long id = requireOrderId(orderId);
|
|
|
+ WorkOrder order = getOrderById(id);
|
|
|
+ // 业务规则:仅已取消工单允许删除
|
|
|
+ if (!WorkOrderStatusEnum.CANCELLED.getCode().equals(order.getOrderStatus())) {
|
|
|
+ throw new ServiceException("仅已取消工单允许删除");
|
|
|
}
|
|
|
// 先清理该工单的操作日志,避免 work_order_log 残留脏数据
|
|
|
- workOrderLogMapper.delete(new LambdaQueryWrapper<WorkOrderLog>().eq(WorkOrderLog::getOrderId, orderId));
|
|
|
- return this.removeById(orderId);
|
|
|
+ workOrderLogMapper.delete(new LambdaQueryWrapper<WorkOrderLog>().eq(WorkOrderLog::getOrderId, id));
|
|
|
+ return this.removeById(id);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建工单分页查询条件(含数据权限片段与设备类别收窄)
|
|
|
+ */
|
|
|
+ private LambdaQueryWrapper<WorkOrder> buildQueryWrapper(WorkOrderPageDTO condition) {
|
|
|
+ LambdaQueryWrapper<WorkOrder> wrapper = buildFilterWrapper(condition, true);
|
|
|
+ wrapper.orderByAsc(WorkOrder::getOrderLevel).orderByAsc(WorkOrder::getOrderStatus).orderByDesc(WorkOrder::getFaultTime).orderByDesc(WorkOrder::getCreateTime);
|
|
|
+ return wrapper;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构建工单筛选条件(数据权限片段 + 业务条件 + 设备类别收窄),列表查询与状态统计共用。
|
|
|
+ *
|
|
|
+ * <p>返回值刻意不含 orderBy:状态统计要按状态分组,带排序在 MySQL ONLY_FULL_GROUP_BY 下会报错。</p>
|
|
|
+ *
|
|
|
+ * @param condition 查询条件
|
|
|
+ * @param withStatus 是否叠加状态筛选。状态统计必须传 false,
|
|
|
+ * 否则点击某个状态后其它状态的计数会全部变成 0。
|
|
|
+ */
|
|
|
+ private LambdaQueryWrapper<WorkOrder> buildFilterWrapper(WorkOrderPageDTO condition, boolean withStatus) {
|
|
|
+ LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
|
|
|
+
|
|
|
+ String dataScope = dataScopeSql(condition);
|
|
|
+ if (StringUtils.isNotEmpty(dataScope)) {
|
|
|
+ wrapper.apply(dataScope);
|
|
|
+ }
|
|
|
+
|
|
|
+ wrapper.eq(condition.getOrderLevel() != null, WorkOrder::getOrderLevel, condition.getOrderLevel()).eq(condition.getDelayApplyStatus() != null, WorkOrder::getDelayApplyStatus, condition.getDelayApplyStatus()).eq(StringUtils.isNotEmpty(condition.getDeviceId()), WorkOrder::getDeviceId, condition.getDeviceId()).eq(StringUtils.isNotEmpty(condition.getDeviceCode()), WorkOrder::getDeviceCode, condition.getDeviceCode()).ge(StringUtils.isNotEmpty(condition.getFaultTimeStart()), WorkOrder::getFaultTime, DateUtils.parseDate(condition.getFaultTimeStart())).le(StringUtils.isNotEmpty(condition.getFaultTimeEnd()), WorkOrder::getFaultTime, DateUtils.parseDate(condition.getFaultTimeEnd()));
|
|
|
+
|
|
|
+ if (withStatus) {
|
|
|
+ List<Integer> statusList = parseStatusList(condition.getOrderStatus());
|
|
|
+ wrapper.in(!statusList.isEmpty(), WorkOrder::getOrderStatus, statusList);
|
|
|
+ }
|
|
|
+
|
|
|
+ String keyword = StringUtils.isEmpty(condition.getKeyword()) ? null : condition.getKeyword().trim();
|
|
|
+ if (StringUtils.isNotEmpty(keyword)) {
|
|
|
+ wrapper.and(w -> w.like(WorkOrder::getOrderNo, keyword).or().like(WorkOrder::getDeviceCode, keyword).or().like(WorkOrder::getOrderDesc, keyword));
|
|
|
+ }
|
|
|
+
|
|
|
+ applyDeviceTypeScope(wrapper, condition.getDeviceTypeParentId());
|
|
|
+ return wrapper;
|
|
|
}
|
|
|
|
|
|
- private String dataScopeSql(WorkOrderPageDTO dto) {
|
|
|
- Map<String, Object> params = dto.getParams();
|
|
|
+ /**
|
|
|
+ * Map 值转 Long:兼容不同驱动下 COUNT(*) 返回 Integer / Long / BigInteger / String 的差异
|
|
|
+ */
|
|
|
+ private Long toLong(Object value) {
|
|
|
+ if (value == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ if (value instanceof Number) {
|
|
|
+ return ((Number) value).longValue();
|
|
|
+ }
|
|
|
+ String text = String.valueOf(value).trim();
|
|
|
+ return StringUtils.isNumeric(text) ? Long.valueOf(text) : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析并剥离数据权限片段中多余的 AND / OR 前缀。
|
|
|
+ *
|
|
|
+ * <p>DataScopeAspect 注入的片段形如 {@code AND (...)},而 wrapper.apply 会自动追加 AND,
|
|
|
+ * 故此处仅保留条件本体。</p>
|
|
|
+ */
|
|
|
+ private String dataScopeSql(WorkOrderPageDTO condition) {
|
|
|
+ Map<String, Object> params = condition.getParams();
|
|
|
if (params == null || params.isEmpty()) {
|
|
|
return null;
|
|
|
}
|
|
|
@@ -447,9 +544,6 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
return null;
|
|
|
}
|
|
|
String sql = String.valueOf(dataScope).trim();
|
|
|
- if (sql.length() == 0) {
|
|
|
- return null;
|
|
|
- }
|
|
|
String upper = sql.toUpperCase(Locale.ROOT);
|
|
|
if (upper.startsWith("AND ")) {
|
|
|
sql = sql.substring(4).trim();
|
|
|
@@ -465,89 +559,53 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
* <p>过滤链路:work_order.device_id → equipment_base.equipment_id
|
|
|
* → equipment_base.equipment_type_id → equipment_type.id → equipment_type.parent_type_id。
|
|
|
* 采用 EXISTS 子查询而非 JOIN,避免设备表一对多时放大分页结果集,也不影响已有的
|
|
|
- * 数据权限片段(dataScope)与 MyBatis-Plus 分页计数。
|
|
|
+ * 数据权限片段(dataScope)与 MyBatis-Plus 分页计数。</p>
|
|
|
*
|
|
|
- * <p>parentTypeId 通过 MyBatis-Plus 的 apply 占位符 {0} 注入,由框架做 SQL 注入检查,不拼接原始入参。
|
|
|
+ * <p>parentTypeId 通过 MyBatis-Plus 的 apply 占位符 {0} 注入,由框架做 SQL 注入检查,不拼接原始入参。</p>
|
|
|
*
|
|
|
- * @param wrapper 工单查询条件
|
|
|
- * @param parentTypeId 设备父类别ID,为空表示不限制
|
|
|
+ * @param wrapper 工单查询条件
|
|
|
+ * @param parentTypeId 设备父类别ID,为空表示不限制
|
|
|
*/
|
|
|
private void applyDeviceTypeScope(LambdaQueryWrapper<WorkOrder> wrapper, String parentTypeId) {
|
|
|
if (StringUtils.isEmpty(parentTypeId)) {
|
|
|
return;
|
|
|
}
|
|
|
- wrapper.apply("EXISTS (SELECT 1 FROM app_user.equipment_base eb "
|
|
|
- + "INNER JOIN app_user.equipment_type et ON et.id = eb.equipment_type_id "
|
|
|
- + "WHERE eb.equipment_id = app_user.work_order.device_id "
|
|
|
- + "AND et.parent_type_id = {0})",
|
|
|
- parentTypeId.trim());
|
|
|
+ wrapper.apply("EXISTS (SELECT 1 FROM app_user.equipment_base eb " + "INNER JOIN app_user.equipment_type et ON et.id = eb.equipment_type_id " + "WHERE eb.equipment_id = app_user.work_order.device_id " + "AND et.parent_type_id = {0})", parentTypeId.trim());
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public List<TreeSelect> assignableDeptTree() {
|
|
|
- return assignScopeSupport.assignableDeptTree();
|
|
|
- }
|
|
|
-
|
|
|
- @Override
|
|
|
- public List<SysUser> assignableUsers(Long deptId) {
|
|
|
- return assignScopeSupport.assignableUsers(deptId);
|
|
|
+ /**
|
|
|
+ * 状态更新基础条件:固定工单ID 并刷新更新时间
|
|
|
+ */
|
|
|
+ private LambdaUpdateWrapper<WorkOrder> updateWrapper(Long orderId) {
|
|
|
+ return new LambdaUpdateWrapper<WorkOrder>().set(WorkOrder::getUpdateTime, new Date()).eq(WorkOrder::getOrderId, orderId);
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- public List<WorkOrderPushVO> selectPendingOrOverdueWorkOrders(Date now) {
|
|
|
- Date currentTime = now != null ? now : new Date();
|
|
|
- List<WorkOrder> orders = baseMapper.selectPendingOrOverdue(currentTime);
|
|
|
- List<WorkOrderPushVO> result = new ArrayList<>();
|
|
|
- if (orders == null || orders.isEmpty()) {
|
|
|
- return result;
|
|
|
- }
|
|
|
- for (WorkOrder order : orders) {
|
|
|
- Integer status = order.getOrderStatus();
|
|
|
- Date effectiveDeadline = order.getDelayedDeadline() != null ? order.getDelayedDeadline() : order.getPlanFinishTime();
|
|
|
- boolean pending = Objects.equals(WorkOrderStatusEnum.PENDING_DISPATCH.getCode(), status) || Objects.equals(WorkOrderStatusEnum.PENDING_ACCEPT.getCode(), status);
|
|
|
- boolean overdue = effectiveDeadline != null && effectiveDeadline.before(currentTime);
|
|
|
- if (!pending && !overdue) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- WorkOrderPushVO item = new WorkOrderPushVO();
|
|
|
- item.setOrderId(order.getOrderId());
|
|
|
- item.setOrderNo(order.getOrderNo());
|
|
|
- item.setOrderStatus(status);
|
|
|
- item.setOrderStatusDesc(WorkOrderStatusEnum.getDescByCode(status));
|
|
|
- item.setOrderLevel(order.getOrderLevel());
|
|
|
- item.setDeptId(order.getDeptId());
|
|
|
- item.setReceiveUser(order.getReceiveUser() == null ? null : String.valueOf(order.getReceiveUser()));
|
|
|
- item.setDeviceId(order.getDeviceId());
|
|
|
- item.setDeviceCode(order.getDeviceCode());
|
|
|
- item.setOrderDesc(order.getOrderDesc());
|
|
|
- item.setPlanFinishTime(order.getPlanFinishTime());
|
|
|
- item.setDelayedDeadline(order.getDelayedDeadline());
|
|
|
- item.setEffectiveDeadline(effectiveDeadline);
|
|
|
- item.setOverdue(overdue);
|
|
|
- if (overdue) {
|
|
|
- item.setOverdueType(order.getDelayedDeadline() != null ? "DELAYED_DEADLINE" : "PLAN_FINISH_TIME");
|
|
|
- item.setOverdueMinutes(Math.max(0L, (currentTime.getTime() - effectiveDeadline.getTime()) / 60000L));
|
|
|
- } else {
|
|
|
- item.setOverdueType("PENDING");
|
|
|
- item.setOverdueMinutes(0L);
|
|
|
- }
|
|
|
- item.setCreateTime(order.getCreateTime());
|
|
|
- item.setUpdateTime(order.getUpdateTime());
|
|
|
- result.add(item);
|
|
|
+ /**
|
|
|
+ * 带前置状态校验的状态推进:WHERE 中同时匹配 orderId 与当前状态,
|
|
|
+ * 返回受影响行数是否为 1,天然抵御并发重复提交。
|
|
|
+ *
|
|
|
+ * @param orderId 工单ID
|
|
|
+ * @param expectedStatus 期望的当前状态
|
|
|
+ * @param targetStatus 目标状态
|
|
|
+ * @param extra 附加字段写入(可为 null)
|
|
|
+ * @return 是否更新成功
|
|
|
+ */
|
|
|
+ private boolean transition(Long orderId, Integer expectedStatus, Integer targetStatus, Consumer<LambdaUpdateWrapper<WorkOrder>> extra) {
|
|
|
+ LambdaUpdateWrapper<WorkOrder> wrapper = updateWrapper(orderId).set(WorkOrder::getOrderStatus, targetStatus).eq(expectedStatus != null, WorkOrder::getOrderStatus, expectedStatus);
|
|
|
+ if (extra != null) {
|
|
|
+ extra.accept(wrapper);
|
|
|
}
|
|
|
- return result;
|
|
|
+ return this.update(wrapper);
|
|
|
}
|
|
|
|
|
|
- // ==================== 私有方法 ====================
|
|
|
|
|
|
/**
|
|
|
- * 加载工单并校验操作人是责任人本人(超级管理员放行)
|
|
|
+ * 校验工单当前状态,不符合时抛出业务异常
|
|
|
*/
|
|
|
- private WorkOrder loadForOperate(WorkOrderOperateDTO dto, String actionName) {
|
|
|
- Long orderId = requireOrderId(dto == null ? null : dto.getOrderId());
|
|
|
- WorkOrder order = getOrderById(orderId);
|
|
|
- requireResponsible(order, currentUserId(), actionName);
|
|
|
- return order;
|
|
|
+ private void requireStatus(WorkOrder order, WorkOrderStatusEnum expected, String message) {
|
|
|
+ if (!Objects.equals(expected.getCode(), order.getOrderStatus())) {
|
|
|
+ throw new ServiceException(message);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -566,10 +624,8 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
}
|
|
|
|
|
|
private WorkOrder getOrderById(Long orderId) {
|
|
|
- if (orderId == null) {
|
|
|
- throw new ServiceException("工单ID不能为空");
|
|
|
- }
|
|
|
- WorkOrder order = this.getById(orderId);
|
|
|
+ Long id = requireOrderId(orderId);
|
|
|
+ WorkOrder order = this.getById(id);
|
|
|
if (order == null) {
|
|
|
throw new ServiceException("工单不存在或已被删除");
|
|
|
}
|
|
|
@@ -583,6 +639,19 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
return orderId;
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 校验同一设备是否已存在未办结工单
|
|
|
+ */
|
|
|
+ private void checkDeviceOrderConflict(String deviceId) {
|
|
|
+ if (StringUtils.isEmpty(deviceId)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ boolean exists = this.exists(new LambdaQueryWrapper<WorkOrder>().eq(WorkOrder::getDeviceId, deviceId).in(WorkOrder::getOrderStatus, WorkOrderStatusEnum.activeCodes()));
|
|
|
+ if (exists) {
|
|
|
+ throw new ServiceException("该设备已有处理中的工单,需结案或取消后才能创建新工单");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 写入工单操作日志(操作人取当前登录用户)
|
|
|
*/
|
|
|
@@ -598,28 +667,14 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
workOrderLogMapper.insert(logEntry);
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 校验同一设备是否已存在未办结工单
|
|
|
- */
|
|
|
- private void checkDeviceOrderConflict(String deviceId) {
|
|
|
- if (StringUtils.isEmpty(deviceId)) {
|
|
|
- return;
|
|
|
- }
|
|
|
- Long count = this.lambdaQuery().eq(WorkOrder::getDeviceId, deviceId).in(WorkOrder::getOrderStatus, WorkOrderStatusEnum.activeCodes()).count();
|
|
|
- if (count != null && count > 0) {
|
|
|
- throw new ServiceException("该设备已有处理中的工单,需结案或取消后才能创建新工单");
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
/**
|
|
|
* 生成全局唯一工单编号:WO + 14位时间戳 + 4位随机数
|
|
|
*/
|
|
|
private String generateOrderNo() {
|
|
|
- String pattern = "yyyyMMddHHmmss";
|
|
|
+ SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
|
|
|
for (int i = 0; i < ORDER_NO_MAX_RETRY; i++) {
|
|
|
- String no = ORDER_NO_PREFIX + new SimpleDateFormat(pattern).format(new Date()) + String.format("%04d", ThreadLocalRandom.current().nextInt(ORDER_NO_RANDOM_BOUND));
|
|
|
- Long count = this.count(new LambdaQueryWrapper<WorkOrder>().eq(WorkOrder::getOrderNo, no));
|
|
|
- if (count == null || count == 0) {
|
|
|
+ String no = ORDER_NO_PREFIX + formatter.format(new Date()) + String.format("%04d", ThreadLocalRandom.current().nextInt(ORDER_NO_RANDOM_BOUND));
|
|
|
+ if (!this.exists(new LambdaQueryWrapper<WorkOrder>().eq(WorkOrder::getOrderNo, no))) {
|
|
|
return no;
|
|
|
}
|
|
|
}
|
|
|
@@ -649,6 +704,45 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 转换为 WebSocket 推送视图,非「待办或超时」的工单返回 null
|
|
|
+ */
|
|
|
+ private WorkOrderPushVO toPushVO(WorkOrder order, Date currentTime) {
|
|
|
+ Integer status = order.getOrderStatus();
|
|
|
+ Date effectiveDeadline = order.getDelayedDeadline() != null ? order.getDelayedDeadline() : order.getPlanFinishTime();
|
|
|
+ boolean pending = Objects.equals(WorkOrderStatusEnum.PENDING_DISPATCH.getCode(), status) || Objects.equals(WorkOrderStatusEnum.PENDING_ACCEPT.getCode(), status);
|
|
|
+ boolean overdue = effectiveDeadline != null && effectiveDeadline.before(currentTime);
|
|
|
+ if (!pending && !overdue) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ WorkOrderPushVO item = new WorkOrderPushVO();
|
|
|
+ item.setOrderId(order.getOrderId());
|
|
|
+ item.setOrderNo(order.getOrderNo());
|
|
|
+ item.setOrderStatus(status);
|
|
|
+ item.setOrderStatusDesc(WorkOrderStatusEnum.getDescByCode(status));
|
|
|
+ item.setOrderLevel(order.getOrderLevel());
|
|
|
+ item.setDeptId(order.getDeptId());
|
|
|
+ item.setReceiveUser(order.getReceiveUser() == null ? null : String.valueOf(order.getReceiveUser()));
|
|
|
+ item.setDeviceId(order.getDeviceId());
|
|
|
+ item.setDeviceCode(order.getDeviceCode());
|
|
|
+ item.setOrderDesc(order.getOrderDesc());
|
|
|
+ item.setPlanFinishTime(order.getPlanFinishTime());
|
|
|
+ item.setDelayedDeadline(order.getDelayedDeadline());
|
|
|
+ item.setEffectiveDeadline(effectiveDeadline);
|
|
|
+ item.setOverdue(overdue);
|
|
|
+ if (overdue) {
|
|
|
+ item.setOverdueType(order.getDelayedDeadline() != null ? "DELAYED_DEADLINE" : "PLAN_FINISH_TIME");
|
|
|
+ item.setOverdueMinutes(Math.max(0L, (currentTime.getTime() - effectiveDeadline.getTime()) / 60000L));
|
|
|
+ } else {
|
|
|
+ item.setOverdueType("PENDING");
|
|
|
+ item.setOverdueMinutes(0L);
|
|
|
+ }
|
|
|
+ item.setCreateTime(order.getCreateTime());
|
|
|
+ item.setUpdateTime(order.getUpdateTime());
|
|
|
+ return item;
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 同步隐患状态:根据工单处理结果更新关联隐患记录的状态和整改反馈
|
|
|
*/
|
|
|
@@ -695,7 +789,7 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
* 按本页实际涉及的用户ID批量查询并构建「用户ID → 姓名」映射。
|
|
|
*
|
|
|
* <p>此前是全量拉取用户表(selectAllEnabledUser)再建 Map,用户量大时每次分页查询都要扫描全表。
|
|
|
- * 实际只用到当页记录的 receiveUser / dispatchUser,故改为按 ID 精确查询。
|
|
|
+ * 实际只用到当页记录的 receiveUser / dispatchUser,故改为按 ID 精确查询。</p>
|
|
|
*
|
|
|
* @param records 当前页工单记录
|
|
|
* @return 用户ID → 姓名的映射;查询失败时返回空 Map(降级为展示ID,不影响主流程)
|
|
|
@@ -744,7 +838,7 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
|
|
|
/**
|
|
|
* 获取当前登录用户姓名:用户昵称(nickName)优先,缺省时回退到登录账号。
|
|
|
- * 工单操作日志展示姓名比账号更直观,与 WaterSupplyAlarmServiceImpl 保持一致。
|
|
|
+ * 工单操作日志展示姓名比账号更直观。
|
|
|
*/
|
|
|
private String currentUserName() {
|
|
|
try {
|
|
|
@@ -763,17 +857,12 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
|
|
|
}
|
|
|
|
|
|
private List<Integer> parseStatusList(String orderStatus) {
|
|
|
+ if (StringUtils.isEmpty(orderStatus)) {
|
|
|
+ return Collections.emptyList();
|
|
|
+ }
|
|
|
return Arrays.stream(orderStatus.split(",")).map(String::trim).filter(StringUtils::isNumeric).map(Integer::valueOf).filter(WorkOrderStatusEnum::isValid).collect(Collectors.toList());
|
|
|
}
|
|
|
|
|
|
- private String content(WorkOrderOperateDTO dto) {
|
|
|
- return dto == null ? null : dto.getContent();
|
|
|
- }
|
|
|
-
|
|
|
- private String attach(WorkOrderOperateDTO dto) {
|
|
|
- return dto == null ? null : dto.getAttachUrl();
|
|
|
- }
|
|
|
-
|
|
|
private String buildDesc(String prefix, String detail) {
|
|
|
boolean prefixEmpty = StringUtils.isEmpty(prefix);
|
|
|
boolean detailEmpty = StringUtils.isEmpty(detail);
|