Procházet zdrojové kódy

工单流程重构

zengy před 1 měsícem
rodič
revize
161d8b47a6
19 změnil soubory, kde provedl 1029 přidání a 744 odebrání
  1. 10 7
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/DeviceAbnormal/DeviceAbnormalController.java
  2. 10 6
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/DeviceMaintain/DeviceMaintainController.java
  3. 4 5
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/alarm/AlarmController.java
  4. 147 136
      pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/BusinessWorkOrderController.java
  5. 7 9
      pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/MonitorAlarm/service/impl/WaterSupplyAlarmServiceImpl.java
  6. 19 122
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/WorkOrder.java
  7. 24 3
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/WorkOrderLog.java
  8. 6 3
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/vo/WorkOrderPushVO.java
  9. 4 3
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/drill/service/DrillAlarmSyncService.java
  10. 63 43
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/BusinessWorkOrderService.java
  11. 5 4
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/DrillAlarmSyncService.java
  12. 589 339
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/BusinessWorkOrderServiceImpl.java
  13. 13 17
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/WaterSupplyDeviceServiceImpl.java
  14. 77 40
      pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/WorkOrderServiceImpl.java
  15. 8 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/system/mapper/SysUserMapper.java
  16. 8 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/system/service/ISysUserService.java
  17. 16 0
      pipe-network-service/zksy-system/src/main/java/com/zksy/system/service/impl/SysUserServiceImpl.java
  18. 9 7
      pipe-network-service/zksy-system/src/main/resources/mapper/WorkOrderMapper.xml
  19. 10 0
      pipe-network-service/zksy-system/src/main/resources/mapper/system/SysUserMapper.xml

+ 10 - 7
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/DeviceAbnormal/DeviceAbnormalController.java

@@ -13,6 +13,8 @@ import com.zksy.common.enums.BusinessType;
 import com.zksy.common.utils.poi.ExcelUtil;
 import com.zksy.common.utils.SecurityUtils;
 import com.zksy.common.exception.ServiceException;
+import com.zksy.base.enums.WorkOrderStatusEnum;
+import com.zksy.ops.dto.WorkOrderDispatchDTO;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -75,18 +77,19 @@ public class DeviceAbnormalController {
                 return AjaxResult.error("派单失败:工单创建未返回有效工单ID");
             }
             if (receiveUserId == null) {
-                order.setOrderStatus(1);
+                order.setOrderStatus(WorkOrderStatusEnum.PENDING_DISPATCH.getCode());
                 return AjaxResult.success("工单创建成功,待派单", order);
             }
-            Long operatorId = SecurityUtils.getUserId();
-            String operatorName = SecurityUtils.getUsername();
-            boolean dispatched = businessWorkOrderService.dispatchOrder(order.getOrderId(), receiveUserId,
-                    vo.getMaintainRemark(), operatorId, operatorName);
+            WorkOrderDispatchDTO dispatchDTO = new WorkOrderDispatchDTO();
+            dispatchDTO.setOrderId(order.getOrderId());
+            dispatchDTO.setHandlerId(receiveUserId);
+            dispatchDTO.setRemarks(vo.getMaintainRemark());
+            boolean dispatched = businessWorkOrderService.dispatch(dispatchDTO);
             if (!dispatched) {
                 return AjaxResult.error("派单失败:工单仍为待派单状态,请稍后重试");
             }
-            order.setReceiveUser(String.valueOf(receiveUserId));
-            order.setOrderStatus(2);
+            order.setReceiveUser(receiveUserId);
+            order.setOrderStatus(WorkOrderStatusEnum.PENDING_ACCEPT.getCode());
             return AjaxResult.success("派单成功", order);
         } catch (ServiceException e) {
             log.warn("供水异常设备派单业务失败: {}", e.getMessage());

+ 10 - 6
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/WaterSupply/DeviceMaintain/DeviceMaintainController.java

@@ -11,6 +11,8 @@ import com.zksy.common.core.domain.AjaxResult;
 import com.zksy.common.enums.BusinessType;
 import com.zksy.common.utils.SecurityUtils;
 import com.zksy.common.exception.ServiceException;
+import com.zksy.base.enums.WorkOrderStatusEnum;
+import com.zksy.ops.dto.WorkOrderDispatchDTO;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import io.swagger.annotations.ApiParam;
@@ -46,17 +48,19 @@ public class DeviceMaintainController {
                 return AjaxResult.error("派单失败:工单创建未返回有效工单ID");
             }
             if (receiveUserId == null) {
-                order.setOrderStatus(1);
+                order.setOrderStatus(WorkOrderStatusEnum.PENDING_DISPATCH.getCode());
                 return AjaxResult.success("工单创建成功,待派单", order);
             }
-            boolean dispatched = businessWorkOrderService.dispatchOrder(order.getOrderId(),
-                    receiveUserId, vo.getMaintainRemark(), SecurityUtils.getUserId(),
-                    SecurityUtils.getUsername());
+            WorkOrderDispatchDTO dispatchDTO = new WorkOrderDispatchDTO();
+            dispatchDTO.setOrderId(order.getOrderId());
+            dispatchDTO.setHandlerId(receiveUserId);
+            dispatchDTO.setRemarks(vo.getMaintainRemark());
+            boolean dispatched = businessWorkOrderService.dispatch(dispatchDTO);
             if (!dispatched) {
                 return AjaxResult.error("派单失败:工单仍为待派单状态,请稍后重试");
             }
-            order.setReceiveUser(String.valueOf(receiveUserId));
-            order.setOrderStatus(2);
+            order.setReceiveUser(receiveUserId);
+            order.setOrderStatus(WorkOrderStatusEnum.PENDING_ACCEPT.getCode());
             return AjaxResult.success("派单成功", order);
         } catch (ServiceException e) {
             log.warn("创建维修派单业务失败: {}", e.getMessage());

+ 4 - 5
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/alarm/AlarmController.java

@@ -77,13 +77,12 @@ public class AlarmController {
         order.setAlarmId(alarmId);
         order.setDeviceId(equipment != null ? equipment.getEquipmentId() : alarm.getDeviceCode());
         order.setDeviceCode(alarm.getDeviceCode());
-        order.setOrderType(1);
         Object orderLevel = body.get("orderLevel");
         order.setOrderLevel(orderLevel == null ? mapAlarmLevel(alarm.getAlarmLevel()) : Integer.valueOf(orderLevel.toString()));
         Object orderDesc = body.get("orderDesc");
         order.setOrderDesc(orderDesc == null || orderDesc.toString().trim().isEmpty()
                 ? buildOrderDesc(alarm) : orderDesc.toString());
-        Long orderId = businessWorkOrderService.createWorkOrder(order);
+        Long orderId = businessWorkOrderService.addWorkOrder(order);
 
         alarm.setAlarmStatus(1);
         alarm.setHandleTime(LocalDateTime.now());
@@ -95,9 +94,9 @@ public class AlarmController {
     }
 
     private Integer mapAlarmLevel(Integer alarmLevel) {
-        if (alarmLevel == null || alarmLevel == 1) return 1;
-        if (alarmLevel == 4) return 3;
-        return 2;
+        if (alarmLevel == null || alarmLevel == 1) return 0;
+        if (alarmLevel == 4) return 2;
+        return 1;
     }
 
     private String buildOrderDesc(AlarmData alarm) {

+ 147 - 136
pipe-network-service/zksy-admin/src/main/java/com/zksy/web/controller/gasbasic/BusinessWorkOrderController.java

@@ -1,199 +1,210 @@
 package com.zksy.web.controller.gasbasic;
 
-import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.zksy.base.domain.WorkOrder;
-import com.zksy.base.domain.WorkOrderLog;
 import com.zksy.base.service.BusinessWorkOrderService;
-import com.zksy.common.core.controller.BaseController;
 import com.zksy.common.core.domain.AjaxResult;
 import com.zksy.common.utils.DateUtils;
-import com.zksy.common.utils.SecurityUtils;
+import com.zksy.ops.dto.WorkOrderDelayApplyDTO;
+import com.zksy.ops.dto.WorkOrderDispatchDTO;
+import com.zksy.ops.dto.WorkOrderOperateDTO;
+import com.zksy.ops.dto.WorkOrderPageDTO;
+import com.zksy.ops.dto.WorkOrderVerifyDTO;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-import java.util.*;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Date;
+import java.util.Map;
 
 /**
- * 业务工单管理 Controller
+ * 业务工单管理 Controller(历史兼容层)
+ *
+ * <p>保留历史 URL,供排水/供水/井盖等旧页面调用;内部统一委托 {@link BusinessWorkOrderService},
+ * 与新工单流程(/api/ops/pworkOrder)共用同一套状态机,避免出现两套状态定义。</p>
+ *
+ * @author zksy
  */
 @Slf4j
 @RestController
 @RequestMapping("/api/work-order")
-@Api(tags = "业务工单管理")
-public class BusinessWorkOrderController extends BaseController {
+@Api(tags = "业务工单管理(历史兼容)")
+public class BusinessWorkOrderController {
 
     @Autowired
     private BusinessWorkOrderService businessWorkOrderService;
 
+    /**
+     * 历史接口:查询工单列表
+     *
+     * <p>orderType 与 equipmentType 已废弃,不再参与过滤</p>
+     */
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:query')")
     @GetMapping("/list")
-    @ApiOperation("查询工单列表(equipmentType:gas-燃气 drainage-排水,为空默认燃气)")
-    public AjaxResult list(Integer orderType, Integer orderLevel, String orderStatus, String deviceCode, String keyword, String equipmentType, @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize) {
-
-        IPage<WorkOrder> page = businessWorkOrderService.selectWorkOrderList(orderType, orderLevel, orderStatus, deviceCode, keyword, equipmentType, pageNum, pageSize);
-
-        Map<String, Object> data = new HashMap<>();
-        data.put("records", page.getRecords());
-        data.put("total", page.getTotal());
-        data.put("pageNum", page.getCurrent());
-        data.put("pageSize", page.getSize());
-        return AjaxResult.success(data);
+    @ApiOperation("查询工单列表(orderType/equipmentType 已废弃)")
+    public AjaxResult list(@RequestParam(required = false) Integer orderType,
+                           @RequestParam(required = false) Integer orderLevel,
+                           @RequestParam(required = false) String orderStatus,
+                           @RequestParam(required = false) String deviceCode,
+                           @RequestParam(required = false) String keyword,
+                           @RequestParam(required = false) String equipmentType,
+                           @RequestParam(defaultValue = "1") Integer pageNum,
+                           @RequestParam(defaultValue = "10") Integer pageSize) {
+        WorkOrderPageDTO dto = new WorkOrderPageDTO();
+        dto.setOrderLevel(orderLevel);
+        dto.setOrderStatus(orderStatus);
+        dto.setDeviceCode(deviceCode);
+        dto.setKeyword(keyword);
+        dto.setPageNum(pageNum == null ? 1L : pageNum.longValue());
+        dto.setPageSize(pageSize == null ? 10L : pageSize.longValue());
+        return AjaxResult.success(businessWorkOrderService.findByPage(dto));
     }
 
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:add')")
     @PostMapping("/add")
-    @ApiOperation("手动新增工单(故障维修/日常巡检/设备保养)")
+    @ApiOperation("手动新增故障工单")
     public AjaxResult add(@RequestBody WorkOrder workOrder) {
-        if (workOrder.getOrderDesc() == null || workOrder.getOrderDesc().isEmpty()) {
-            return AjaxResult.error("工单描述不能为空");
-        }
-        Long orderId = businessWorkOrderService.createWorkOrder(workOrder);
-        return AjaxResult.success("工单创建成功", orderId);
+        return AjaxResult.success("工单创建成功", businessWorkOrderService.addWorkOrder(workOrder));
     }
 
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:query')")
     @GetMapping("/{orderId}")
     @ApiOperation("查询工单详情(含操作日志)")
     public AjaxResult detail(@PathVariable Long orderId) {
-        WorkOrder order = businessWorkOrderService.getWorkOrderDetail(orderId);
-        List<WorkOrderLog> logs = businessWorkOrderService.getWorkOrderLogs(orderId);
-
-        Map<String, Object> data = new HashMap<>();
-        data.put("order", order);
-        data.put("logs", logs);
-        return AjaxResult.success(data);
+        return AjaxResult.success(businessWorkOrderService.getDetailById(orderId));
     }
 
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:dispatch')")
     @PostMapping("/dispatch")
-    @ApiOperation("派单")
+    @ApiOperation("派单(兼容 handlerIds 多人入参,仅取第一位作为责任人)")
     public AjaxResult dispatch(@RequestBody Map<String, Object> body) {
-        if (body.get("orderId") == null) {
-            return AjaxResult.error("工单ID不能为空");
-        }
-        Long orderId = Long.valueOf(body.get("orderId").toString());
-        String remarks = body.get("remarks") != null ? body.get("remarks").toString() : "";
-        Long userId = SecurityUtils.getUserId();
-        String userName = SecurityUtils.getUsername();
-
-        // 支持多人派单:优先使用 handlerIds 数组,兼容旧版 handlerId
-        if (body.get("handlerIds") != null) {
-            // 使用 List<?> 兼容 Jackson 解析出的 Integer/Long/String 类型
-            List<?> handlerIdList = (List<?>) body.get("handlerIds");
-            if (handlerIdList.isEmpty()) {
-                return AjaxResult.error("接单人不能为空");
-            }
-            List<Long> handlerIds = new ArrayList<>();
-            for (Object item : handlerIdList) {
-                if (item != null) {
-                    handlerIds.add(Long.valueOf(item.toString()));
-                }
+        WorkOrderDispatchDTO dto = new WorkOrderDispatchDTO();
+        dto.setOrderId(toLong(body.get("orderId")));
+        dto.setHandlerId(toLong(body.get("handlerId")));
+        dto.setRemarks(toStringValue(body.get("remarks")));
+        if (dto.getHandlerId() == null && body.get("handlerIds") instanceof java.util.List) {
+            java.util.List<?> ids = (java.util.List<?>) body.get("handlerIds");
+            if (ids.isEmpty()) {
+                return AjaxResult.error("责任人不能为空");
             }
-            businessWorkOrderService.batchDispatchOrder(orderId, handlerIds, remarks, userId, userName);
-        } else if (body.get("handlerId") != null) {
-            Long handlerId = Long.valueOf(body.get("handlerId").toString());
-            businessWorkOrderService.dispatchOrder(orderId, handlerId, remarks, userId, userName);
-        } else {
-            return AjaxResult.error("接单人不能为空");
+            dto.setHandlerId(toLong(ids.get(0)));
         }
-        return AjaxResult.success("派单成功");
+        return businessWorkOrderService.dispatch(dto)
+                ? AjaxResult.success("派单成功") : AjaxResult.error("派单失败");
     }
 
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:accept')")
     @PutMapping("/verify")
-    @ApiOperation("核查")
-    public AjaxResult verify(@RequestBody Map<String, Object> body) {
-        Long orderId = Long.valueOf(body.get("orderId").toString());
-        String handleResult = body.get("handleResult") != null ? body.get("handleResult").toString() : "";
-        String solution = body.get("solution") != null ? body.get("solution").toString() : "";
-        Long userId = SecurityUtils.getUserId();
-        String userName = SecurityUtils.getUsername();
+    @ApiOperation("核查接单(历史:核查)")
+    public AjaxResult verify(@RequestBody WorkOrderOperateDTO dto) {
+        return businessWorkOrderService.accept(dto)
+                ? AjaxResult.success("核查完成") : AjaxResult.error("核查失败");
+    }
 
-        businessWorkOrderService.verifyOrder(orderId, handleResult, solution, userId, userName);
-        return AjaxResult.success("核查完成");
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:accept')")
+    @PutMapping("/accept")
+    @ApiOperation("接单(历史接口)")
+    public AjaxResult accept(@RequestBody WorkOrderOperateDTO dto) {
+        return businessWorkOrderService.accept(dto)
+                ? AjaxResult.success("接单成功") : AjaxResult.error("接单失败");
     }
 
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:process')")
     @PutMapping("/process")
-    @ApiOperation("处理")
-    public AjaxResult process(@RequestBody Map<String, Object> body) {
-        Long orderId = Long.valueOf(body.get("orderId").toString());
-        String handleResult = body.get("handleResult") != null ? body.get("handleResult").toString() : "";
-        String solution = body.get("solution") != null ? body.get("solution").toString() : "";
-        String maintenanceBeforeImg = body.get("maintenanceBeforeImg") != null ? body.get("maintenanceBeforeImg").toString() : "";
-        Long userId = SecurityUtils.getUserId();
-        String userName = SecurityUtils.getUsername();
+    @ApiOperation("工单处置")
+    public AjaxResult process(@RequestBody WorkOrderOperateDTO dto) {
+        return businessWorkOrderService.process(dto)
+                ? AjaxResult.success("处置成功") : AjaxResult.error("处置失败");
+    }
 
-        businessWorkOrderService.processOrder(orderId, handleResult, solution, maintenanceBeforeImg, userId, userName);
-        return AjaxResult.success("处理完成");
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:process')")
+    @PutMapping("/start")
+    @ApiOperation("开始处理(历史接口,等价处置)")
+    public AjaxResult start(@RequestBody WorkOrderOperateDTO dto) {
+        return businessWorkOrderService.process(dto)
+                ? AjaxResult.success("处理成功") : AjaxResult.error("处理失败");
     }
 
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:submitVerify')")
     @PutMapping("/complete")
-    @ApiOperation("完成/提交验收")
-    public AjaxResult complete(@RequestBody Map<String, Object> body) {
-        Long orderId = Long.valueOf(body.get("orderId").toString());
-        String result = body.get("result") != null ? body.get("result").toString() : "";
-        String maintenanceAfterImg = body.get("maintenanceAfterImg") != null
-                ? body.get("maintenanceAfterImg").toString() : "";
-        Long userId = SecurityUtils.getUserId();
-        String userName = SecurityUtils.getUsername();
-
-        businessWorkOrderService.completeOrder(orderId, result, maintenanceAfterImg, userId, userName);
-        return AjaxResult.success("提交验收成功");
+    @ApiOperation("提交验收(历史:完成)")
+    public AjaxResult complete(@RequestBody WorkOrderOperateDTO dto) {
+        return businessWorkOrderService.submitVerify(dto)
+                ? AjaxResult.success("提交验收成功") : AjaxResult.error("提交验收失败");
     }
 
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:verify')")
     @PutMapping("/close")
-    @ApiOperation("验收通过/结案,同步联动隐患状态为已整改")
-    public AjaxResult close(@RequestBody Map<String, Object> body) {
-        Long orderId = Long.valueOf(body.get("orderId").toString());
-        String result = body.get("result") != null ? body.get("result").toString() : "";
-        Long userId = SecurityUtils.getUserId();
-        String userName = SecurityUtils.getUsername();
-        businessWorkOrderService.closeOrder(orderId, result, userId, userName);
-        return AjaxResult.success("验收通过");
+    @ApiOperation("验收通过/结案")
+    public AjaxResult close(@RequestBody WorkOrderOperateDTO dto) {
+        WorkOrderVerifyDTO verifyDTO = new WorkOrderVerifyDTO();
+        verifyDTO.setOrderId(dto.getOrderId());
+        verifyDTO.setPassed(Boolean.TRUE);
+        verifyDTO.setContent(dto.getContent());
+        verifyDTO.setAttachUrl(dto.getAttachUrl());
+        return businessWorkOrderService.verify(verifyDTO)
+                ? AjaxResult.success("验收通过") : AjaxResult.error("验收失败");
     }
 
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:cancel')")
+    @PutMapping("/reject")
+    @ApiOperation("驳回工单(历史接口,等价取消)")
+    public AjaxResult reject(@RequestBody WorkOrderOperateDTO dto) {
+        return businessWorkOrderService.cancel(dto)
+                ? AjaxResult.success("驳回成功") : AjaxResult.error("驳回失败");
+    }
+
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:delay')")
+    @PutMapping("/delay")
+    @ApiOperation("申请延期")
+    public AjaxResult delay(@RequestBody WorkOrderDelayApplyDTO dto) {
+        return businessWorkOrderService.applyDelay(dto)
+                ? AjaxResult.success("延期申请已提交") : AjaxResult.error("延期申请失败");
+    }
+
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:cancel')")
+    @DeleteMapping("/{orderId}")
+    @ApiOperation("取消工单")
+    public AjaxResult cancel(@PathVariable Long orderId, @RequestBody(required = false) WorkOrderOperateDTO dto) {
+        WorkOrderOperateDTO cancelDTO = dto == null ? new WorkOrderOperateDTO() : dto;
+        cancelDTO.setOrderId(orderId);
+        return businessWorkOrderService.cancel(cancelDTO)
+                ? AjaxResult.success("取消工单成功") : AjaxResult.error("取消工单失败");
+    }
+
+    @PreAuthorize("@ss.hasPermi('ops:pworkOrder:add')")
     @PostMapping("/createFromHazard")
     @ApiOperation("从隐患创建工单")
     public AjaxResult createFromHazard(@RequestBody Map<String, Object> body) {
-        String hazardId = (String) body.get("hazardId");
-        String hazardNo = (String) body.get("hazardNo");
-        String hazardDesc = (String) body.get("hazardDesc");
-        String equipmentId = (String) body.get("equipmentId");
-        String hazardLevel = (String) body.get("hazardLevel");
-        String hazardImages = (String) body.get("hazardImages");
-        String planFinishTimeStr = (String) body.get("planFinishTime");
-
-        if (hazardId == null || equipmentId == null) {
-            return AjaxResult.error("隐患ID和设备ID不能为空");
-        }
-
-        Date planFinishTime = null;
-        if (StringUtils.isNotEmpty(planFinishTimeStr)) {
-            planFinishTime = DateUtils.parseDate(planFinishTimeStr);
-        }
-
-        Long orderId = businessWorkOrderService.createWorkOrderFromHazard(hazardId, hazardNo, hazardDesc, equipmentId, hazardLevel, hazardImages, planFinishTime);
+        String planFinishTimeStr = toStringValue(body.get("planFinishTime"));
+        Date planFinishTime = StringUtils.isNotEmpty(planFinishTimeStr) ? DateUtils.parseDate(planFinishTimeStr) : null;
+        Long orderId = businessWorkOrderService.createWorkOrderFromHazard(
+                toStringValue(body.get("hazardId")),
+                toStringValue(body.get("hazardNo")),
+                toStringValue(body.get("hazardDesc")),
+                toStringValue(body.get("equipmentId")),
+                toStringValue(body.get("hazardLevel")),
+                toStringValue(body.get("hazardImages")),
+                planFinishTime);
         return AjaxResult.success("工单创建成功", orderId);
     }
 
-    @PutMapping("/reject")
-    @ApiOperation("驳回工单(待派单/待接单 -> 已驳回)")
-    public AjaxResult reject(@RequestBody Map<String, Object> body) {
-        Long orderId = Long.valueOf(body.get("orderId").toString());
-        String reason = body.get("reason") != null ? body.get("reason").toString() : "";
-        Long userId = SecurityUtils.getUserId();
-        String userName = SecurityUtils.getUsername();
-
-        businessWorkOrderService.rejectOrder(orderId, reason, userId, userName);
-        return AjaxResult.success("驳回成功");
+    private Long toLong(Object value) {
+        return value == null ? null : Long.valueOf(value.toString());
     }
 
-    @DeleteMapping("/{orderId}")
-    @ApiOperation("取消工单")
-    public AjaxResult cancel(@PathVariable Long orderId) {
-        Long userId = SecurityUtils.getUserId();
-        String userName = SecurityUtils.getUsername();
-
-        businessWorkOrderService.cancelOrder(orderId, userId, userName);
-        return AjaxResult.success("取消工单成功");
+    private String toStringValue(Object value) {
+        return value == null ? null : String.valueOf(value);
     }
 }

+ 7 - 9
pipe-network-service/zksy-system/src/main/java/com/zksy/WaterSupply/MonitorAlarm/service/impl/WaterSupplyAlarmServiceImpl.java

@@ -333,22 +333,21 @@ public class WaterSupplyAlarmServiceImpl implements IWaterSupplyAlarmService {
         workOrder.setAlarmId(alarm.getId());
         workOrder.setDeviceId(equipment != null ? equipment.getEquipmentId() : alarm.getDeviceCode());
         workOrder.setDeviceCode(alarm.getDeviceCode());
-        workOrder.setOrderType(inDTO.getOrderType() == null ? 1 : inDTO.getOrderType());
         workOrder.setOrderLevel(inDTO.getOrderLevel() == null
                 ? mapAlarmLevelToOrderLevel(alarm.getAlarmLevel()) : inDTO.getOrderLevel());
         workOrder.setOrderDesc(StrUtil.isNotBlank(inDTO.getOrderDesc()) ? inDTO.getOrderDesc()
                 : buildDefaultOrderDesc(alarm));
         workOrder.setDeptId(inDTO.getDeptId());
         workOrder.setPlanFinishTime(inDTO.getPlanFinishTime());
-        workOrder.setOrderStatus(1); // 1-待派单
+        workOrder.setOrderStatus(0); // 0-待派单
         workOrder.setCreateTime(new java.util.Date());
         workOrder.setUpdateTime(new java.util.Date());
         workOrderMapper.insert(workOrder);
 
-        // 写工单操作日志(1-创建工单)
+        // 写工单操作日志(0-创建工单)
         WorkOrderLog workOrderLog = new WorkOrderLog();
         workOrderLog.setOrderId(workOrder.getOrderId());
-        workOrderLog.setOperType(1);
+        workOrderLog.setOperType(0);
         workOrderLog.setOperDesc("由供水监测报警[" + alarm.getWarningType() + "]派发工单:" + workOrder.getOrderNo());
         workOrderLog.setOperUserId(currentUserId());
         workOrderLog.setOperUserName(currentUserName());
@@ -366,16 +365,16 @@ public class WaterSupplyAlarmServiceImpl implements IWaterSupplyAlarmService {
     }
 
     /**
-     * 报警等级 -> 工单优先级:1级→紧急,2/3级→一般,4级→低
+     * 报警等级 -> 工单优先级:1级→紧急(0),2/3级→一般(1),4级→低(2)
      */
     private Integer mapAlarmLevelToOrderLevel(Integer alarmLevel) {
         if (alarmLevel == null || alarmLevel == 1) {
-            return 1;
+            return 0;
         }
         if (alarmLevel == 4) {
-            return 3;
+            return 2;
         }
-        return 2;
+        return 1;
     }
 
     private String buildDefaultOrderDesc(AlarmData alarm) {
@@ -445,7 +444,6 @@ public class WaterSupplyAlarmServiceImpl implements IWaterSupplyAlarmService {
             // 审核通过即上传至运维工单,工单默认待派单状态
             AlarmDispatchInDTO dispatch = new AlarmDispatchInDTO();
             dispatch.setAlarmId(alarm.getId());
-            dispatch.setOrderType(1);
             dispatch.setOrderLevel(mapAlarmLevelToOrderLevel(alarm.getAlarmLevel()));
             dispatch.setOrderDesc(StrUtil.isBlank(inDTO.getAuditOpinion())
                     ? null : inDTO.getAuditOpinion());

+ 19 - 122
pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/WorkOrder.java

@@ -14,7 +14,6 @@ import java.util.Date;
 
 /**
  * 运维工单表
- * @TableName work_order
  */
 @TableName(value = "work_order", schema = "app_user")
 @Data
@@ -23,180 +22,78 @@ public class WorkOrder implements Serializable {
 
     @TableField(exist = false)
     private static final long serialVersionUID = 1L;
-
-    /**
-     * 工单ID
-     */
     @ApiModelProperty(value = "工单ID")
     @TableId(type = IdType.AUTO)
     private Long orderId;
-
-    /**
-     * 工单编号
-     */
     @ApiModelProperty(value = "工单编号")
     private String orderNo;
-
-    /**
-     * 关联报警ID
-     */
     @ApiModelProperty(value = "报警ID")
     private String alarmId;
-
-    /**
-     * 关联案件ID
-     */
     @ApiModelProperty(value = "案件ID")
     private Long caseId;
-
-    /**
-     * 案件编号
-     */
     @ApiModelProperty(value = "案件编号")
     private String caseNo;
-
-    /**
-     * 关联隐患ID
-     */
     @ApiModelProperty(value = "隐患ID")
     private String hazardId;
-
-    /**
-     * 隐患编号
-     */
     @ApiModelProperty(value = "隐患编号")
     private String hazardNo;
-
-    /**
-     * 关联设备ID
-     */
     @ApiModelProperty(value = "设备ID")
     private String deviceId;
-
-    /**
-     * 设备编码
-     */
     @ApiModelProperty(value = "设备编码")
     private String deviceCode;
-
-    /**
-     * 工单类型:1-故障维修 2-日常巡检 3-设备保养
-     */
-    @ApiModelProperty(value = "工单类型:1-故障维修 2-日常巡检 3-设备保养")
-    private Integer orderType;
-
-    /**
-     * 工单优先级:1-紧急 2-一般 3-低
-     */
-    @ApiModelProperty(value = "工单优先级:1-紧急 2-一般 3-低")
+    @ApiModelProperty(value = "故障发生时间")
+    @JsonFormat(locale = "zh", pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date faultTime;
+    @ApiModelProperty(value = "工单优先级:0-紧急 1-一般 2-低")
     private Integer orderLevel;
-
-    /**
-     * 工单问题描述
-     */
     @ApiModelProperty(value = "工单问题描述")
     private String orderDesc;
-
-    /**
-     * 负责部门ID
-     */
-    @ApiModelProperty(value = "负责部门ID")
+    @ApiModelProperty(value = "归属部门ID,取派单时指定责任人所属部门;待派单阶段为创建人部门。数据权限按此字段做「本部门及子部门」过滤")
     private Long deptId;
-
-    /**
-     * 派单人ID
-     */
     @ApiModelProperty(value = "派单人ID")
     private Long dispatchUser;
-
-    /**
-     * 派单时间
-     */
     @ApiModelProperty(value = "派单时间")
     @JsonFormat(locale = "zh",pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
     private Date dispatchTime;
-
-    /**
-     * 接单运维人员ID(多个用逗号分隔)
-     */
-    @ApiModelProperty(value = "接单运维人员ID,多个用逗号分隔")
-    private String receiveUser;
-
-    /**
-     * 工单状态:1-待派单 2-待接单 3-已接单 4-处理中 5-待验收 6-已结案 7-已驳回 8-延期审核 9-已延期
-     */
-    @ApiModelProperty(value = "工单状态:1-待派单 2-待接单 3-已接单 4-处理中 5-待验收 6-已结案 7-已驳回 8-延期审核 9-已延期")
+    @ApiModelProperty(value = "责任人用户ID")
+    private Long receiveUser;
+    @ApiModelProperty(value = "工单状态:0-待派单 1-待接单 2-已接单 3-处理中 4-待验收 5-已结案 6-延期中 7-已取消 (见WorkOrderStatusEnum)")
     private Integer orderStatus;
-
-    /**
-     * 延期原因
-     */
+    @ApiModelProperty(value = "延期申请状态:0-无延期申请 1-待延期审核 2-延期审核通过 3-延期审核驳回 见 WorkOrderDelayApplyStatusEnum)")
+    private Integer delayApplyStatus;
     @ApiModelProperty(value = "延期原因")
     private String delayedReason;
-
-    /**
-     * 延期截止时间
-     */
     @ApiModelProperty(value = "延期截止时间")
     @JsonFormat(locale = "zh",pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
     private Date delayedDeadline;
-
-    /**
-     * 计划完成时间
-     */
     @ApiModelProperty(value = "计划完成时间")
     @JsonFormat(locale = "zh",pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
     private Date planFinishTime;
-
-    /**
-     * 实际完成时间
-     */
     @ApiModelProperty(value = "实际完成时间")
     @JsonFormat(locale = "zh",pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
     private Date realFinishTime;
-
-    /**
-     * 维修处置结果
-     */
     @ApiModelProperty(value = "维修处置结果")
     private String repairResult;
-
-    /**
-     * 维修前图片 URL,多个图片使用逗号分隔
-     */
-    @ApiModelProperty(value = "维修前图片 URL")
+    @ApiModelProperty(value = "维修前图片URL,多个图片使用逗号分隔")
     private String maintenanceBeforeImg;
-
-    /**
-     * 维修后图片 URL,多个图片使用逗号分隔
-     */
-    @ApiModelProperty(value = "维修后图片 URL")
+    @ApiModelProperty(value = "维修后图片URL,多个图片使用逗号分隔")
     private String maintenanceAfterImg;
-
-    /**
-     * 验收人ID
-     */
     @ApiModelProperty(value = "验收人ID")
     private Long verifyUser;
-
-    /**
-     * 验收时间
-     */
     @ApiModelProperty(value = "验收时间")
     @JsonFormat(locale = "zh",pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
     private Date verifyTime;
-
-    /**
-     * 创建时间
-     */
     @ApiModelProperty(value = "创建时间")
     @JsonFormat(locale = "zh",pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
     private Date createTime;
-
-    /**
-     * 更新时间
-     */
     @ApiModelProperty(value = "更新时间")
     @JsonFormat(locale = "zh",pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
     private Date updateTime;
+
+    @ApiModelProperty(value = "责任人姓名")
+    @TableField(exist = false)
+    private String receiveUserName;
+    @ApiModelProperty(value = "派单人姓名")
+    @TableField(exist = false)
+    private String dispatchUserName;
 }

+ 24 - 3
pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/WorkOrderLog.java

@@ -11,10 +11,10 @@ import lombok.Data;
 
 import java.io.Serializable;
 import java.util.Date;
+import java.util.List;
 
 /**
  * 工单操作日志表
- * @TableName work_order_log
  */
 @TableName(value = "work_order_log", schema = "app_user")
 @Data
@@ -38,17 +38,38 @@ public class WorkOrderLog implements Serializable {
     private Long orderId;
 
     /**
-     * 操作类型:1-创建工单 2-派单 3-接单 4-开始维修 5-提交验收 6-验收通过 7-驳回工单 8-申请延期
+     * 操作类型:0-创建工单 1-派单 2-接单 3-处置 4-提交验收 5-验收通过 6-验收驳回 7-取消工单
+     * 8-申请延期 9-延期审核通过 10-延期审核驳回(见 WorkOrderLogOperTypeEnum)
      */
-    @ApiModelProperty(value = "操作类型:1-创建工单 2-派单 3-接单 4-开始维修 5-提交验收 6-验收通过 7-驳回工单 8-申请延期 9-延期审核通过")
+    @ApiModelProperty(value = "操作类型:0-创建工单 1-派单 2-接单 3-处置 4-提交验收 5-验收通过 6-验收驳回 7-取消工单 8-申请延期 9-延期审核通过 10-延期审核驳回")
     private Integer operType;
 
+    /**
+     * 操作类型描述(非数据库字段,查询时回填)
+     */
+    @ApiModelProperty(value = "操作类型描述")
+    @TableField(exist = false)
+    private String operTypeDesc;
+
     /**
      * 操作详情描述
      */
     @ApiModelProperty(value = "操作详情描述")
     private String operDesc;
 
+    /**
+     * 操作附件 URL,多个使用逗号分隔
+     */
+    @ApiModelProperty(value = "操作附件 URL,多个使用逗号分隔")
+    private String attachUrl;
+
+    /**
+     * 操作附件 URL 列表(非数据库字段,查询时回填,便于前端预览)
+     */
+    @ApiModelProperty(value = "操作附件列表")
+    @TableField(exist = false)
+    private List<String> attachList;
+
     /**
      * 操作人ID
      */

+ 6 - 3
pipe-network-service/zksy-system/src/main/java/com/zksy/base/domain/vo/WorkOrderPushVO.java

@@ -24,16 +24,19 @@ public class WorkOrderPushVO implements Serializable {
     @ApiModelProperty("工单编号")
     private String orderNo;
 
-    @ApiModelProperty("工单状态:1-待派单 2-待接单 3-已接单 4-处理中 5-待验收 8-延期审核 9-已延期")
+    @ApiModelProperty("工单状态:0-待派单 1-待接单 2-已接单 3-处理中 4-待验收 5-已结案 6-延期中 7-已取消")
     private Integer orderStatus;
 
-    @ApiModelProperty("工单优先级")
+    @ApiModelProperty("工单状态描述")
+    private String orderStatusDesc;
+
+    @ApiModelProperty("工单优先级:1-紧急 2-一般 3-低")
     private Integer orderLevel;
 
     @ApiModelProperty("所属部门ID")
     private Long deptId;
 
-    @ApiModelProperty("接单人ID,多个使用逗号分隔")
+    @ApiModelProperty("责任人用户ID")
     private String receiveUser;
 
     @ApiModelProperty("设备ID")

+ 4 - 3
pipe-network-service/zksy-system/src/main/java/com/zksy/base/drill/service/DrillAlarmSyncService.java

@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.zksy.base.alarm.domain.AlarmData;
 import com.zksy.base.domain.CaseInfo;
 import com.zksy.base.domain.EquipmentBase;
+import com.zksy.base.enums.WorkOrderLogOperTypeEnum;
+import com.zksy.base.enums.WorkOrderStatusEnum;
 import com.zksy.base.domain.WorkOrder;
 import com.zksy.base.domain.WorkOrderLog;
 import com.zksy.base.mapper.CaseInfoMapper;
@@ -54,7 +56,7 @@ public class DrillAlarmSyncService {
         String typeId = equipmentBase.getEquipmentTypeId();
         boolean needCreateCase = CASE_REQUIRED_TYPE_IDS.contains(typeId);
         int caseLevel = alarmData.getAlarmLevel() != null ? alarmData.getAlarmLevel() : 4;
-        int orderLevel = caseLevel <= 2 ? 1 : (caseLevel == 3 ? 2 : 3);
+        int orderLevel = caseLevel <= 2 ? 0 : (caseLevel == 3 ? 1 : 2);
         String desc = alarmData.getWarningType() != null ? alarmData.getWarningType() : "设备告警";
         if (alarmData.getRemark() != null) {
             desc += "-" + alarmData.getRemark();
@@ -78,10 +80,9 @@ public class DrillAlarmSyncService {
         workOrder.setAlarmId(alarmData.getId());
         workOrder.setDeviceId(equipmentBase.getEquipmentId());
         workOrder.setDeviceCode(alarmData.getDeviceCode());
-        workOrder.setOrderType(1);
         workOrder.setOrderLevel(orderLevel);
         workOrder.setOrderDesc(desc);
-        workOrder.setOrderStatus(1);
+        workOrder.setOrderStatus(WorkOrderStatusEnum.PENDING_DISPATCH.getCode());
         if (caseInfo != null) {
             workOrder.setCaseId(caseInfo.getCaseId());
             workOrder.setCaseNo(caseInfo.getCaseNo());

+ 63 - 43
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/BusinessWorkOrderService.java

@@ -1,32 +1,42 @@
 package com.zksy.base.service;
 
-import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.zksy.base.domain.WorkOrder;
 import com.zksy.base.domain.WorkOrderLog;
 import com.zksy.base.domain.vo.WorkOrderPushVO;
+import com.zksy.common.core.domain.TreeSelect;
+import com.zksy.common.core.domain.entity.SysUser;
+import com.zksy.ops.dto.WorkOrderDelayApplyDTO;
+import com.zksy.ops.dto.WorkOrderDelayAuditDTO;
+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.WorkOrderVerifyDTO;
 
 import java.util.Date;
 import java.util.List;
 
 /**
- * 业务工单管理 Service(独立于已有 WorkOrderService)
+ * 运维工单 Service(ops 工单流程)
+ *
+ * <p>状态流转:0待派单 →(派单) 1待接单 →(核查) 2已接单 →(处置) 3处理中
+ * →(提交验收) 4待验收 →(验收通过) 5已结案;旁路:6延期中、7已取消</p>
+ *
+ * @author zksy
  */
 public interface BusinessWorkOrderService extends IService<WorkOrder> {
 
     /**
-     * 分页查询工单列表(支持多条件筛选+排序)
-     * @param equipmentType 设备类型:gas-燃气 drainage-排水,为空查询全部
+     * 分页查询工单列表(含数据权限过滤)
      */
-    IPage<WorkOrder> selectWorkOrderList(Integer orderType, Integer orderLevel,
-                                         String orderStatus, String deviceCode,
-                                         String keyword, String equipmentType,
-                                         Integer pageNum, Integer pageSize);
+    Page<WorkOrder> findByPage(WorkOrderPageDTO dto);
 
     /**
-     * 查询工单详情
+     * 查询工单详情(含操作日志)
      */
-    WorkOrder getWorkOrderDetail(Long orderId);
+    WorkOrderDetailVO getDetailById(Long orderId);
 
     /**
      * 查询工单操作日志列表
@@ -34,70 +44,80 @@ public interface BusinessWorkOrderService extends IService<WorkOrder> {
     List<WorkOrderLog> getWorkOrderLogs(Long orderId);
 
     /**
-     * 派单
+     * 新增故障工单(初始状态:待派单)
+     *
+     * @return 工单ID
      */
-    boolean dispatchOrder(Long orderId, Long handlerId, String remarks, Long operUserId, String operUserName);
+    Long addWorkOrder(WorkOrder workOrder);
 
     /**
-     * 批量派单(支持多人)
+     * 派单:待派单 → 待接单
      */
-    boolean batchDispatchOrder(Long orderId, List<Long> handlerIds, String remarks, Long operUserId, String operUserName);
+    boolean dispatch(WorkOrderDispatchDTO dto);
 
     /**
-     * 核查
+     * 核查接单:待接单 → 已接单
      */
-    boolean verifyOrder(Long orderId, String handleResult, String solution, Long operUserId, String operUserName);
+    boolean accept(WorkOrderOperateDTO dto);
 
     /**
-     * 处理
+     * 处置:已接单/处理中/延期中 → 处理中(首次处置同步写入维修前附件)
      */
-    boolean processOrder(Long orderId, String handleResult, String solution, String maintenanceBeforeImg, Long operUserId, String operUserName);
+    boolean process(WorkOrderOperateDTO dto);
 
     /**
-     * 完成/提交验收
+     * 提交验收:处理中/延期中 → 待验收(同步写入维修后附件)
      */
-    boolean completeOrder(Long orderId, String result, String maintenanceAfterImg, Long operUserId, String operUserName);
+    boolean submitVerify(WorkOrderOperateDTO dto);
 
     /**
-     * 取消工单
+     * 验收:待验收 → 已结案(通过) / 处理中(驳回)
      */
-    boolean cancelOrder(Long orderId, Long operUserId, String operUserName);
+    boolean verify(WorkOrderVerifyDTO dto);
 
     /**
-     * 驳回工单(待派单/待接单 -> 已驳回)
-     * @param reason 驳回原因
+     * 取消工单:待派单/待接单/已接单/处理中/待验收 → 已取消
      */
-    boolean rejectOrder(Long orderId, String reason, Long operUserId, String operUserName);
+    boolean cancel(WorkOrderOperateDTO dto);
 
     /**
-     * 验收通过/结案(status 5→6),同步联动隐患状态为"已整改"
+     * 延期申请:处理中且无延期申请 → 待延期审核
      */
-    boolean closeOrder(Long orderId, String result, Long operUserId, String operUserName);
+    boolean applyDelay(WorkOrderDelayApplyDTO dto);
 
     /**
-     * 从隐患创建工单
-     * @param hazardId 隐患ID
-     * @param hazardNo 隐患编号
-     * @param hazardDesc 隐患描述
-     * @param equipmentId 设备ID
-     * @param hazardLevel 隐患等级
-     * @param hazardImages 隐患图片
-     * @param planFinishTime 计划完成时间
-     * @return 工单ID
+     * 延期审核:待延期审核 → 审核通过 / 审核驳回
      */
-    Long createWorkOrderFromHazard(String hazardId, String hazardNo, String hazardDesc, String equipmentId,
-                                    String hazardLevel, String hazardImages, Date planFinishTime);
+    boolean auditDelay(WorkOrderDelayAuditDTO dto);
 
     /**
-     * 手动新增工单(故障维修/日常巡检/设备保养)
-     * @param workOrder 工单信息(orderType/orderLevel/orderDesc/deviceId/deviceCode/planFinishTime)
+     * 删除工单,并级联删除该工单下的操作日志(work_order_log)
+     *
+     * @param orderId 工单ID
+     * @return 是否删除成功
+     */
+    boolean deleteWithLogs(Long orderId);
+
+    /**
+     * 获取可指派部门树
+     */
+    List<TreeSelect> assignableDeptTree();
+
+    /**
+     * 查询可指派用户
+     */
+    List<SysUser> assignableUsers(Long deptId);
+
+    /**
+     * 从隐患创建工单
+     *
      * @return 工单ID
      */
-    Long createWorkOrder(WorkOrder workOrder);
+    Long createWorkOrderFromHazard(String hazardId, String hazardNo, String hazardDesc, String equipmentId,
+                                    String hazardLevel, String hazardImages, Date planFinishTime);
 
     /**
-     * 查询需要通过 WebSocket 提醒的工单。
-     * 状态 1/2 始终纳入;其他未办结状态仅在计划完成时间或延期截止时间超时后纳入。
+     * 查询需要通过 WebSocket 提醒的工单(待派单/待接单 或 已超时)
      */
     List<WorkOrderPushVO> selectPendingOrOverdueWorkOrders(Date now);
 }

+ 5 - 4
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/DrillAlarmSyncService.java

@@ -5,6 +5,8 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.zksy.base.alarm.domain.AlarmData;
 import com.zksy.base.domain.CaseInfo;
 import com.zksy.base.domain.EquipmentBase;
+import com.zksy.base.enums.WorkOrderLogOperTypeEnum;
+import com.zksy.base.enums.WorkOrderStatusEnum;
 import com.zksy.base.domain.WorkOrder;
 import com.zksy.base.domain.WorkOrderLog;
 import com.zksy.base.mapper.CaseInfoMapper;
@@ -48,7 +50,7 @@ public class DrillAlarmSyncService {
         String typeId = equipmentBase.getEquipmentTypeId();
         boolean needCreateCase = CASE_REQUIRED_TYPE_IDS.contains(typeId);
         int caseLevel = alarmData.getAlarmLevel() != null ? alarmData.getAlarmLevel() : 4;
-        int orderLevel = caseLevel <= 2 ? 1 : (caseLevel == 3 ? 2 : 3);
+        int orderLevel = caseLevel <= 2 ? 0 : (caseLevel == 3 ? 1 : 2);
         String desc = alarmData.getWarningType() != null ? alarmData.getWarningType() : "设备告警";
         if (alarmData.getRemark() != null) {
             desc += "-" + alarmData.getRemark();
@@ -72,11 +74,10 @@ public class DrillAlarmSyncService {
         workOrder.setAlarmId(alarmData.getId());
         workOrder.setDeviceId(equipmentBase.getEquipmentId());
         workOrder.setDeviceCode(alarmData.getDeviceCode());
-        workOrder.setOrderType(1);
         workOrder.setOrderLevel(orderLevel);
         workOrder.setOrderDesc(desc);
         workOrder.setCreateTime(DateUtil.date());
-        workOrder.setOrderStatus(1);
+        workOrder.setOrderStatus(WorkOrderStatusEnum.PENDING_DISPATCH.getCode());
         if (caseInfo != null) {
             workOrder.setCaseId(caseInfo.getCaseId());
             workOrder.setCaseNo(caseInfo.getCaseNo());
@@ -85,7 +86,7 @@ public class DrillAlarmSyncService {
         log.info("同步新增工单成功,orderNo={}", workOrder.getOrderNo());
         WorkOrderLog workOrderLog = new WorkOrderLog();
         workOrderLog.setOrderId(workOrder.getOrderId());
-        workOrderLog.setOperType(1);
+        workOrderLog.setOperType(WorkOrderLogOperTypeEnum.CREATE.getCode());
         workOrderLog.setOperDesc(desc + ",系统告警自动创建工单");
         workOrderLog.setOperUserName("系统");
         workOrderLog.setOperUserId(-1L);

+ 589 - 339
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/BusinessWorkOrderServiceImpl.java

@@ -1,7 +1,7 @@
 package com.zksy.base.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.zksy.base.domain.EquipmentBase;
@@ -9,33 +9,86 @@ 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.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.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;
+import com.zksy.common.utils.StringUtils;
+import com.zksy.ops.dto.WorkOrderDelayApplyDTO;
+import com.zksy.ops.dto.WorkOrderDelayAuditDTO;
+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.WorkOrderVerifyDTO;
+import com.zksy.ops.support.OpsAssignScopeSupport;
 import com.zksy.system.service.ISysUserService;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
 
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
-import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ThreadLocalRandom;
 import java.util.stream.Collectors;
 
 /**
- * 业务工单管理 Service 实现
+ * 运维工单 Service 实现(ops 工单流程)
+ *
+ * <p>状态流转:0待派单 →(派单) 1待接单 →(核查) 2已接单 →(处置) 3处理中
+ * →(提交验收) 4待验收 →(验收通过) 5已结案;延期审核通过后进入 6延期中;任意前置状态可 7已取消。</p>
+ *
+ * @author zksy
  */
 @Slf4j
 @Service
 public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder> implements BusinessWorkOrderService {
 
+    /**
+     * 工单编号前缀
+     */
+    private static final String ORDER_NO_PREFIX = "WO";
+
+    /**
+     * 工单编号最大生成重试次数(并发唯一性兜底)
+     */
+    private static final int ORDER_NO_MAX_RETRY = 5;
+
+    /**
+     * 工单编号随机数上限(4位)
+     */
+    private static final int ORDER_NO_RANDOM_BOUND = 10000;
+
+    /**
+     * 框架数据范围参数名,与 DataScopeAspect.DATA_SCOPE 保持一致。
+     * zksy-system 模块不依赖 zksy-framework(DataScopeAspect 所在模块),故此处使用字面量。
+     */
+    private static final String DATA_SCOPE = "dataScope";
+
     @Autowired
     private WorkOrderLogMapper workOrderLogMapper;
 
@@ -48,336 +101,422 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
     @Autowired
     private ISysUserService sysUserService;
 
+    @Autowired
+    private OpsAssignScopeSupport assignScopeSupport;
+
     @Override
-    public IPage<WorkOrder> selectWorkOrderList(Integer orderType, Integer orderLevel, String orderStatus, String deviceCode, String keyword, String equipmentType, Integer pageNum, Integer pageSize) {
+    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);
+
+        if (assignScopeSupport.currentUserId() == null) {
+            // 未获取到登录用户时返回空结果,避免越权查看
+            return new Page<>(pageNum, pageSize);
+        }
+
         LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
-        // 按设备类型过滤工单:gas-燃气(父类别ID=3),drainage-排水(typeId为drainage的顶级分类),water-供水;为空查询全部设备工单
-        if (StringUtils.isNotEmpty(equipmentType)) {
-            if ("gas".equals(equipmentType)) {
-                wrapper.inSql(WorkOrder::getDeviceId, "SELECT eb.equipment_id FROM app_user.equipment_base eb " + "INNER JOIN app_user.equipment_type et ON eb.equipment_type_id = et.id " + "WHERE et.parent_type_id = '3'");
-            } else if ("drainage".equals(equipmentType)) {
-                // 排水:typeId为drainage的顶级分类及其全部子类型
-                wrapper.inSql(WorkOrder::getDeviceId, "SELECT eb.equipment_id FROM app_user.equipment_base eb " + "INNER JOIN app_user.equipment_type et ON eb.equipment_type_id = et.id " + "WHERE et.id IN (SELECT id FROM app_user.equipment_type WHERE type_id = 'drainage' AND parent_type_id = '0' " + "UNION SELECT id FROM app_user.equipment_type WHERE parent_type_id = (SELECT id FROM app_user.equipment_type WHERE type_id = 'drainage' AND parent_type_id = '0'))");
-            } else if ("water".equals(equipmentType)) {
-                // 供水:顶级类型名称为“供水”的分类及其全部子类型
-                wrapper.inSql(WorkOrder::getDeviceId, "SELECT eb.equipment_id FROM app_user.equipment_base eb " + "INNER JOIN app_user.equipment_type et ON eb.equipment_type_id = et.id " + "WHERE et.id IN (SELECT id FROM app_user.equipment_type WHERE type_name = '供水' AND parent_type_id = '0' " + "UNION SELECT id FROM app_user.equipment_type WHERE parent_type_id = (SELECT id FROM app_user.equipment_type WHERE type_name = '供水' AND parent_type_id = '0'))");
-            }
+        String dataScope = dataScopeSql(dto);
+        if (StringUtils.isNotEmpty(dataScope)) {
+            wrapper.apply(dataScope);
         }
-        if (orderType != null) wrapper.eq(WorkOrder::getOrderType, orderType);
-        if (orderLevel != null) wrapper.eq(WorkOrder::getOrderLevel, orderLevel);
-        // orderStatus 支持逗号分隔多值,如 1,2,3
-        if (StringUtils.isNotEmpty(orderStatus)) {
-            if (orderStatus.contains(",")) {
-                List<Integer> statusList = Arrays.stream(orderStatus.split(","))
-                        .filter(StringUtils::isNumeric)
-                        .map(Integer::valueOf)
-                        .collect(Collectors.toList());
-                if (!statusList.isEmpty()) {
-                    wrapper.in(WorkOrder::getOrderStatus, statusList);
-                }
-            } else {
-                wrapper.eq(WorkOrder::getOrderStatus, Integer.valueOf(orderStatus));
+        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 (StringUtils.isNotEmpty(deviceCode)) wrapper.eq(WorkOrder::getDeviceCode, deviceCode);
-        if (StringUtils.isNotEmpty(keyword)) {
+        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));
         }
-        wrapper.orderByAsc(WorkOrder::getOrderLevel, WorkOrder::getOrderStatus);
-        wrapper.orderByDesc(WorkOrder::getCreateTime);
-        return this.page(page, wrapper);
+        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;
     }
 
     @Override
-    public WorkOrder getWorkOrderDetail(Long orderId) {
-        WorkOrder order = this.getById(orderId);
-        if (order == null) throw new ServiceException("工单不存在");
-        return order;
+    public WorkOrderDetailVO getDetailById(Long orderId) {
+        WorkOrder order = getOrderById(orderId);
+        fillUserName(Collections.singletonList(order));
+        WorkOrderDetailVO vo = new WorkOrderDetailVO();
+        vo.setOrder(order);
+        vo.setLogs(getWorkOrderLogs(orderId));
+        return vo;
     }
 
     @Override
     public List<WorkOrderLog> getWorkOrderLogs(Long orderId) {
-        LambdaQueryWrapper<WorkOrderLog> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(WorkOrderLog::getOrderId, orderId);
-        wrapper.orderByDesc(WorkOrderLog::getOperTime);
-        return workOrderLogMapper.selectList(wrapper);
+        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()));
+            }
+        }
+        return logs;
     }
 
+    // ==================== 新增 ====================
+
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean dispatchOrder(Long orderId, Long handlerId, String remarks, Long operUserId, String operUserName) {
-        validateDispatchHandler(handlerId);
-        WorkOrder order = this.getById(orderId);
-        if (order == null) throw new ServiceException("工单不存在");
-        if (order.getOrderStatus() != 1) throw new ServiceException("当前工单状态不允许派单");
-
-        WorkOrder entity = new WorkOrder();
-        entity.setOrderId(orderId);
-        entity.setOrderStatus(2);
-        entity.setDispatchUser(operUserId);
-        entity.setDispatchTime(new Date());
-        entity.setReceiveUser(handlerId.toString());
-        this.updateById(entity);
+    public Long addWorkOrder(WorkOrder workOrder) {
+        if (workOrder == null || StringUtils.isEmpty(workOrder.getOrderDesc())) {
+            throw new ServiceException("故障描述不能为空");
+        }
+        checkDeviceOrderConflict(workOrder.getDeviceId());
+        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);
+        }
+        workOrder.setOrderStatus(WorkOrderStatusEnum.PENDING_DISPATCH.getCode());
+        workOrder.setDelayApplyStatus(WorkOrderDelayApplyStatusEnum.NONE.getCode());
+        // 数据按部门隔离:默认归属创建人所在部门(框架 DataScopeAspect 依据该字段过滤)
+        if (workOrder.getDeptId() == null) {
+            workOrder.setDeptId(assignScopeSupport.currentDeptId());
+        }
+        workOrder.setCreateTime(now);
+        workOrder.setUpdateTime(now);
+        this.save(workOrder);
 
-        WorkOrderLog logEntry = new WorkOrderLog();
-        logEntry.setOrderId(orderId);
-        logEntry.setOperType(2);
-        logEntry.setOperDesc("派单给处理人ID:" + handlerId + ",备注:" + (remarks != null ? remarks : ""));
-        logEntry.setOperUserId(operUserId);
-        logEntry.setOperUserName(operUserName);
-        logEntry.setOperTime(new Date());
-        workOrderLogMapper.insert(logEntry);
-        return true;
+        writeLog(workOrder.getOrderId(), WorkOrderLogOperTypeEnum.CREATE, buildDesc("故障描述", workOrder.getOrderDesc()), null);
+        log.info("手动创建工单成功,工单ID={},编号={}", workOrder.getOrderId(), workOrder.getOrderNo());
+        return workOrder.getOrderId();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean batchDispatchOrder(Long orderId, List<Long> handlerIds, String remarks, Long operUserId, String operUserName) {
-        if (handlerIds == null || handlerIds.isEmpty()) {
-            throw new ServiceException("派单人员不能为空");
+    public Long createWorkOrderFromHazard(String hazardId, String hazardNo, String hazardDesc, String equipmentId, String hazardLevel, String hazardImages, Date planFinishTime) {
+        if (StringUtils.isEmpty(equipmentId)) {
+            throw new ServiceException("隐患关联设备不能为空");
         }
-        handlerIds.forEach(this::validateDispatchHandler);
-        WorkOrder order = this.getById(orderId);
-        if (order == null) throw new ServiceException("工单不存在");
-        if (order.getOrderStatus() != 1) throw new ServiceException("当前工单状态不允许派单");
-
-        // 更新工单状态(只更新一次)
-        WorkOrder entity = new WorkOrder();
-        entity.setOrderId(orderId);
-        entity.setOrderStatus(2);
-        entity.setDispatchUser(operUserId);
-        entity.setDispatchTime(new Date());
-        // 多个处理人用逗号分隔存储
-        String receiveUserStr = handlerIds.stream()
-            .map(String::valueOf)
-            .collect(Collectors.joining(","));
-        entity.setReceiveUser(receiveUserStr);
-        this.updateById(entity);
-
-        // 为每个 handler 写入操作日志
-        for (Long handlerId : handlerIds) {
-            WorkOrderLog logEntry = new WorkOrderLog();
-            logEntry.setOrderId(orderId);
-            logEntry.setOperType(2);
-            logEntry.setOperDesc("派单给处理人ID:" + handlerId + ",备注:" + (remarks != null ? remarks : ""));
-            logEntry.setOperUserId(operUserId);
-            logEntry.setOperUserName(operUserName);
-            logEntry.setOperTime(new Date());
-            workOrderLogMapper.insert(logEntry);
+        checkDeviceOrderConflict(equipmentId);
+
+        WorkOrder workOrder = new WorkOrder();
+        workOrder.setOrderNo(generateOrderNo());
+        workOrder.setHazardId(hazardId);
+        workOrder.setHazardNo(hazardNo);
+        workOrder.setDeviceId(equipmentId);
+        workOrder.setDeviceCode(resolveDeviceCode(equipmentId));
+        workOrder.setOrderLevel(mapHazardLevelToOrderLevel(hazardLevel).getCode());
+        workOrder.setOrderDesc(hazardDesc);
+        // 数据按部门隔离:默认归属创建人所在部门(框架 DataScopeAspect 依据该字段过滤)
+        workOrder.setDeptId(assignScopeSupport.currentDeptId());
+        workOrder.setOrderStatus(WorkOrderStatusEnum.PENDING_DISPATCH.getCode());
+        workOrder.setDelayApplyStatus(WorkOrderDelayApplyStatusEnum.NONE.getCode());
+        workOrder.setPlanFinishTime(planFinishTime);
+        if (StringUtils.isNotEmpty(hazardImages)) {
+            workOrder.setMaintenanceBeforeImg(hazardImages);
         }
-        return true;
+        Date now = new Date();
+        workOrder.setFaultTime(now);
+        workOrder.setCreateTime(now);
+        workOrder.setUpdateTime(now);
+        this.save(workOrder);
+
+        writeLog(workOrder.getOrderId(), WorkOrderLogOperTypeEnum.CREATE, buildDesc("由隐患[" + hazardNo + "]自动创建工单", hazardDesc), null);
+        log.info("从隐患[{}]创建工单成功,工单ID={}", hazardId, workOrder.getOrderId());
+        return workOrder.getOrderId();
     }
 
-    private void validateDispatchHandler(Long handlerId) {
+    // ==================== 流程操作 ====================
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean dispatch(WorkOrderDispatchDTO dto) {
+        Long orderId = requireOrderId(dto == null ? null : dto.getOrderId());
+        Long handlerId = dto.getHandlerId();
         if (handlerId == null) {
-            throw new ServiceException("派单人员不能为空");
+            throw new ServiceException("责任人不能为空");
         }
-        if (sysUserService == null) {
-            throw new ServiceException("责任人校验服务不可用");
+        WorkOrder order = getOrderById(orderId);
+        if (!Objects.equals(WorkOrderStatusEnum.PENDING_DISPATCH.getCode(), order.getOrderStatus())) {
+            throw new ServiceException("仅待派单工单允许派单");
         }
-        SysUser user = sysUserService.selectUserById(handlerId);
-        if (user == null || !"0".equals(String.valueOf(user.getStatus()))
-                || !"0".equals(String.valueOf(user.getDelFlag()))) {
-            throw new ServiceException("接单责任人不存在或已停用");
+        assignScopeSupport.validateAssignUserInScope(handlerId);
+        SysUser handler = sysUserService.selectUserById(handlerId);
+        if (handler == null || !UserConstants.NORMAL.equals(String.valueOf(handler.getStatus())) || !UserConstants.NORMAL.equals(String.valueOf(handler.getDelFlag()))) {
+            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)
+                // 数据归属部门跟随派单指定的责任人
+                .set(handler.getDeptId() != null, WorkOrder::getDeptId, handler.getDeptId()).set(WorkOrder::getUpdateTime, now).eq(WorkOrder::getOrderId, orderId).eq(WorkOrder::getOrderStatus, WorkOrderStatusEnum.PENDING_DISPATCH.getCode()));
+
+        String logContent = "派单给责任人:" + userLabel(handler) + (StringUtils.isEmpty(dto.getRemarks()) ? "" : ",备注:" + dto.getRemarks());
+        writeLog(orderId, WorkOrderLogOperTypeEnum.DISPATCH, logContent, null);
+        return true;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean verifyOrder(Long orderId, String handleResult, String solution, Long operUserId, String operUserName) {
-        WorkOrder order = this.getById(orderId);
-        if (order == null) throw new ServiceException("工单不存在");
-        if (!Arrays.asList(2, 3).contains(order.getOrderStatus())) throw new ServiceException("当前工单状态不允许核查");
-
-        WorkOrder entity = new WorkOrder();
-        entity.setOrderId(orderId);
-        entity.setOrderStatus(3);
-        entity.setRepairResult(solution);
-        this.updateById(entity);
+    public boolean accept(WorkOrderOperateDTO dto) {
+        WorkOrder order = loadForOperate(dto, "核查接单");
+        if (!Objects.equals(WorkOrderStatusEnum.PENDING_ACCEPT.getCode(), order.getOrderStatus())) {
+            throw new ServiceException("仅待接单工单允许核查接单");
+        }
+        // 核查接单必须上传现场情况附件
+        if (StringUtils.isEmpty(attach(dto))) {
+            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);
 
-        WorkOrderLog logEntry = new WorkOrderLog();
-        logEntry.setOrderId(orderId);
-        logEntry.setOperType(3);
-        logEntry.setOperDesc("核查结果:" + handleResult + ",说明:" + solution);
-        logEntry.setOperUserId(operUserId);
-        logEntry.setOperUserName(operUserName);
-        logEntry.setOperTime(new Date());
-        workOrderLogMapper.insert(logEntry);
+        writeLog(order.getOrderId(), WorkOrderLogOperTypeEnum.ACCEPT, buildDesc("", content(dto)), attach(dto));
         return true;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean processOrder(Long orderId, String handleResult, String solution, String maintenanceBeforeImg, Long operUserId, String operUserName) {
-        WorkOrder order = this.getById(orderId);
-        if (order == null) throw new ServiceException("工单不存在");
-        if (!Arrays.asList(3, 4).contains(order.getOrderStatus())) throw new ServiceException("当前工单状态不允许处理");
-
-        WorkOrder entity = new WorkOrder();
-        entity.setOrderId(orderId);
-        entity.setOrderStatus(4);
-        entity.setRepairResult(solution);
-        if (StringUtils.isNotBlank(maintenanceBeforeImg)) {
-            entity.setMaintenanceBeforeImg(maintenanceBeforeImg);
+    public boolean process(WorkOrderOperateDTO dto) {
+        WorkOrder order = loadForOperate(dto, "处置");
+        Integer status = order.getOrderStatus();
+        if (!WorkOrderStatusEnum.canProcess(status)) {
+            throw new ServiceException("当前工单状态不允许处置");
         }
-        this.updateById(entity);
+        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);
+
+        syncHazardStatus(order, "整改中", content(dto));
+        writeLog(order.getOrderId(), WorkOrderLogOperTypeEnum.PROCESS, buildDesc("", content(dto)), attach(dto));
+        return true;
+    }
 
-        // 工单处理中 → 同步隐患状态为"整改中"
-        syncHazardStatus(order, "整改中", solution);
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean submitVerify(WorkOrderOperateDTO dto) {
+        WorkOrder order = loadForOperate(dto, "提交验收");
+        Integer status = order.getOrderStatus();
+        if (!WorkOrderStatusEnum.canSubmitVerify(status)) {
+            throw new ServiceException("仅处理中/延期中工单允许提交验收");
+        }
+        // 提交验收必须上传维修后的情况附件
+        if (StringUtils.isEmpty(attach(dto))) {
+            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);
 
-        WorkOrderLog logEntry = new WorkOrderLog();
-        logEntry.setOrderId(orderId);
-        logEntry.setOperType(4);
-        logEntry.setOperDesc("处理结果:" + handleResult + ",说明:" + solution);
-        logEntry.setOperUserId(operUserId);
-        logEntry.setOperUserName(operUserName);
-        logEntry.setOperTime(new Date());
-        workOrderLogMapper.insert(logEntry);
+        writeLog(order.getOrderId(), WorkOrderLogOperTypeEnum.SUBMIT_VERIFY, buildDesc("", content(dto)), attach(dto));
         return true;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public boolean completeOrder(Long orderId, String result, String maintenanceAfterImg, Long operUserId, String operUserName) {
-        WorkOrder order = this.getById(orderId);
-        if (order == null) throw new ServiceException("工单不存在");
-        if (order.getOrderStatus() != 4)
-            throw new ServiceException("当前工单状态不允许提交验收");
-
-        WorkOrder entity = new WorkOrder();
-        entity.setOrderId(orderId);
-        entity.setOrderStatus(5);
-        entity.setRepairResult(result);
-        if (StringUtils.isNotBlank(maintenanceAfterImg)) {
-            entity.setMaintenanceAfterImg(maintenanceAfterImg);
+    public boolean verify(WorkOrderVerifyDTO dto) {
+        Long orderId = requireOrderId(dto == null ? null : dto.getOrderId());
+        Boolean passed = dto == null ? null : dto.getPassed();
+        if (passed == null) {
+            throw new ServiceException("验收结果不能为空");
         }
-        this.updateById(entity);
+        WorkOrder order = getOrderById(orderId);
+        if (!Objects.equals(WorkOrderStatusEnum.PENDING_VERIFY.getCode(), order.getOrderStatus())) {
+            throw new ServiceException("仅待验收工单允许验收");
+        }
+        Long operUserId = currentUserId();
+        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);
 
-        WorkOrderLog logEntry = new WorkOrderLog();
-        logEntry.setOrderId(orderId);
-        logEntry.setOperType(5);
-        logEntry.setOperDesc("提交验收,结果:" + result);
-        logEntry.setOperUserId(operUserId);
-        logEntry.setOperUserName(operUserName);
-        logEntry.setOperTime(new Date());
-        workOrderLogMapper.insert(logEntry);
+        if (passed) {
+            syncHazardStatus(order, "已整改", dto.getContent());
+        }
+        writeLog(orderId, passed ? WorkOrderLogOperTypeEnum.VERIFY_PASS : WorkOrderLogOperTypeEnum.VERIFY_REJECT, buildDesc("验收意见", dto.getContent()), dto.getAttachUrl());
         return true;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public Long createWorkOrderFromHazard(String hazardId, String hazardNo, String hazardDesc, String equipmentId, String hazardLevel, String hazardImages, Date planFinishTime) {
-        // 同一设备存在进行中工单(状态非 6 已结案/7 已驳回)时禁止重复建单
-        checkDeviceOrderConflict(equipmentId, 1, null);
-
-        // 隐患等级映射到工单优先级:0-重大→紧急(1) 1-较大→一般(2) 2-一般→一般(2) 3-轻微→低(3)
-        Integer orderLevel;
-        if ("0".equals(hazardLevel)) {
-            orderLevel = 1;
-        } else if ("3".equals(hazardLevel)) {
-            orderLevel = 3;
-        } else {
-            orderLevel = 2;
+    public boolean cancel(WorkOrderOperateDTO dto) {
+        Long orderId = requireOrderId(dto == null ? null : dto.getOrderId());
+        WorkOrder order = getOrderById(orderId);
+        if (!WorkOrderStatusEnum.canCancel(order.getOrderStatus())) {
+            throw new ServiceException("当前工单状态不允许取消");
+        }
+        if (StringUtils.isEmpty(content(dto))) {
+            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()));
 
-        WorkOrder workOrder = new WorkOrder();
-        workOrder.setOrderNo(generateOrderNo());
-        workOrder.setHazardId(hazardId);
-        workOrder.setHazardNo(hazardNo);
-        workOrder.setDeviceId(equipmentId);
-        // 查询设备信息,填充设备编码
-        if (StringUtils.isNotEmpty(equipmentId)) {
-            EquipmentBase equipment = equipmentBaseMapper.selectById(equipmentId);
-            if (equipment != null) {
-                workOrder.setDeviceCode(equipment.getEquipmentCode());
-            }
+        writeLog(orderId, WorkOrderLogOperTypeEnum.CANCEL, buildDesc("原因", content(dto)), attach(dto));
+        return true;
+    }
+
+    @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("仅处理中工单允许申请延期");
         }
-        workOrder.setOrderType(1); // 默认故障维修
-        workOrder.setOrderLevel(orderLevel);
-        workOrder.setOrderDesc(hazardDesc);
-        workOrder.setOrderStatus(1); // 待派单
-        // 隐患图片作为维修前图片(如有)
-        if (StringUtils.isNotEmpty(hazardImages)) {
-            workOrder.setMaintenanceBeforeImg(hazardImages);
+        if (!Objects.equals(WorkOrderDelayApplyStatusEnum.NONE.getCode(), order.getDelayApplyStatus())) {
+            throw new ServiceException("该工单已提交过延期申请,请勿重复提交");
+        }
+        if (StringUtils.isEmpty(dto.getDelayedReason())) {
+            throw new ServiceException("延期原因不能为空");
+        }
+        Date delayedDeadline = DateUtils.parseDate(dto.getDelayedDeadline());
+        if (delayedDeadline == null) {
+            throw new ServiceException("延期截止时间格式不正确");
         }
-        workOrder.setPlanFinishTime(planFinishTime);
-        workOrder.setCreateTime(new Date());
-        this.save(workOrder);
 
-        // 写入操作日志
-        WorkOrderLog logEntry = new WorkOrderLog();
-        logEntry.setOrderId(workOrder.getOrderId());
-        logEntry.setOperType(1);
-        logEntry.setOperDesc("由隐患[" + hazardId + "]自动创建工单");
-        logEntry.setOperTime(new Date());
-        workOrderLogMapper.insert(logEntry);
+        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()));
 
-        log.info("从隐患[{}]创建工单成功,工单ID={}", hazardId, workOrder.getOrderId());
-        return workOrder.getOrderId();
+        writeLog(orderId, WorkOrderLogOperTypeEnum.DELAY_APPLY, "申请延期至 " + dto.getDelayedDeadline() + ",原因:" + dto.getDelayedReason(), dto.getAttachUrl());
+        return true;
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public Long createWorkOrder(WorkOrder workOrder) {
-        // 同一设备存在进行中工单(状态非 6 已结案/7 已驳回)时禁止重复建单
-        if (workOrder.getOrderType() == null) {
-            workOrder.setOrderType(1);
+    public boolean auditDelay(WorkOrderDelayAuditDTO dto) {
+        Long orderId = requireOrderId(dto == null ? null : dto.getOrderId());
+        Boolean approved = dto.getApproved();
+        if (approved == null) {
+            throw new ServiceException("延期审核结果不能为空");
         }
-        checkDeviceOrderConflict(workOrder.getDeviceId(), workOrder.getOrderType(), workOrder.getAlarmId());
-        if (workOrder.getOrderLevel() == null) {
-            workOrder.setOrderLevel(2);
+        WorkOrder order = getOrderById(orderId);
+        if (!Objects.equals(WorkOrderDelayApplyStatusEnum.PENDING_AUDIT.getCode(), order.getDelayApplyStatus())) {
+            throw new ServiceException("该工单没有待审核的延期申请");
         }
-        workOrder.setOrderNo(generateOrderNo());
-        // 设备ID存在但未填设备编码时自动补全
-        if (StringUtils.isNotEmpty(workOrder.getDeviceId()) && StringUtils.isEmpty(workOrder.getDeviceCode())) {
-            EquipmentBase equipment = equipmentBaseMapper.selectById(workOrder.getDeviceId());
-            if (equipment != null) {
-                workOrder.setDeviceCode(equipment.getEquipmentCode());
-            }
+        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()));
+
+        writeLog(orderId, 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;
         }
-        workOrder.setOrderStatus(1); // 待派单
-        workOrder.setCreateTime(new Date());
-        this.save(workOrder);
+        // 先清理该工单的操作日志,避免 work_order_log 残留脏数据
+        workOrderLogMapper.delete(new LambdaQueryWrapper<WorkOrderLog>().eq(WorkOrderLog::getOrderId, orderId));
+        return this.removeById(orderId);
+    }
 
-        // 写入操作日志
-        WorkOrderLog logEntry = new WorkOrderLog();
-        logEntry.setOrderId(workOrder.getOrderId());
-        logEntry.setOperType(1);
-        logEntry.setOperDesc("手动创建工单");
-        logEntry.setOperTime(new Date());
-        workOrderLogMapper.insert(logEntry);
+    private String dataScopeSql(WorkOrderPageDTO dto) {
+        Map<String, Object> params = dto.getParams();
+        if (params == null || params.isEmpty()) {
+            return null;
+        }
+        Object dataScope = params.get(DATA_SCOPE);
+        if (dataScope == null) {
+            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();
+        } else if (upper.startsWith("OR ")) {
+            sql = sql.substring(3).trim();
+        }
+        return sql.length() == 0 ? null : sql;
+    }
 
-        log.info("手动创建工单成功,工单ID={},类型={}", workOrder.getOrderId(), workOrder.getOrderType());
-        return workOrder.getOrderId();
+    /**
+     * 按设备父类别收窄工单查询范围(业务口径过滤)。
+     *
+     * <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 分页计数。
+     *
+     * <p>parentTypeId 通过 MyBatis-Plus 的 apply 占位符 {0} 注入,由框架做 SQL 注入检查,不拼接原始入参。
+     *
+     * @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());
+    }
+
+    @Override
+    public List<TreeSelect> assignableDeptTree() {
+        return assignScopeSupport.assignableDeptTree();
+    }
+
+    @Override
+    public List<SysUser> assignableUsers(Long deptId) {
+        return assignScopeSupport.assignableUsers(deptId);
     }
 
     @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 = Integer.valueOf(1).equals(status) || Integer.valueOf(2).equals(status);
+            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());
+            item.setReceiveUser(order.getReceiveUser() == null ? null : String.valueOf(order.getReceiveUser()));
             item.setDeviceId(order.getDeviceId());
             item.setDeviceCode(order.getDeviceCode());
             item.setOrderDesc(order.getOrderDesc());
@@ -385,11 +524,13 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
             item.setDelayedDeadline(order.getDelayedDeadline());
             item.setEffectiveDeadline(effectiveDeadline);
             item.setOverdue(overdue);
-            item.setOverdueType(overdue
-                    ? order.getDelayedDeadline() != null ? "DELAYED_DEADLINE" : "PLAN_FINISH_TIME"
-                    : "PENDING");
-            item.setOverdueMinutes(overdue
-                    ? Math.max(0L, (currentTime.getTime() - effectiveDeadline.getTime()) / 60000L) : 0L);
+            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);
@@ -397,129 +538,126 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
         return result;
     }
 
+    // ==================== 私有方法 ====================
+
     /**
-     * 校验同一业务对象是否已经存在进行中的同类工单。
-     * 带报警身份的工单按设备、工单类型和报警记录去重;没有报警身份的
-     * 手工工单只与同设备的另一条未关联报警工单冲突,避免挡住不同报警原因。
+     * 加载工单并校验操作人是责任人本人(超级管理员放行)
      */
-    private void checkDeviceOrderConflict(String deviceId, Integer orderType, String alarmId) {
-        if (StringUtils.isEmpty(deviceId)) {
-            return;
-        }
-        com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper<WorkOrder> query = this.lambdaQuery()
-                .eq(WorkOrder::getDeviceId, deviceId)
-                .notIn(WorkOrder::getOrderStatus, 6, 7);
-        if (orderType != null) {
-            query.eq(WorkOrder::getOrderType, orderType);
-        }
-        if (StringUtils.isNotBlank(alarmId)) {
-            query.eq(WorkOrder::getAlarmId, alarmId);
-        } else {
-            query.isNull(WorkOrder::getAlarmId);
-        }
-        Long count = query.count();
-        if (count > 0) {
-            throw new ServiceException("该设备已有处理中的工单,需结案或驳回后才能创建新工单");
-        }
+    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;
     }
 
     /**
-     * 生成工单编号:WO-yyyyMMddHHmmssSSS
+     * 校验操作人是工单责任人本人,超级管理员放行
      */
-    private String generateOrderNo() {
-        return "WO-" + new java.text.SimpleDateFormat("yyyyMMddHHmmssSSS").format(new Date());
+    private void requireResponsible(WorkOrder order, Long operUserId, String actionName) {
+        if (operUserId == null) {
+            throw new ServiceException("未获取到登录用户,无法" + actionName);
+        }
+        if (SecurityUtils.isAdmin(operUserId)) {
+            return;
+        }
+        if (order.getReceiveUser() == null || !Objects.equals(order.getReceiveUser(), operUserId)) {
+            throw new ServiceException("仅工单责任人可" + actionName);
+        }
     }
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public boolean cancelOrder(Long orderId, Long operUserId, String operUserName) {
+    private WorkOrder getOrderById(Long orderId) {
+        if (orderId == null) {
+            throw new ServiceException("工单ID不能为空");
+        }
         WorkOrder order = this.getById(orderId);
-        if (order == null) throw new ServiceException("工单不存在");
-        if (order.getOrderStatus() == 6) throw new ServiceException("已结案工单不可取消");
-
-        WorkOrder entity = new WorkOrder();
-        entity.setOrderId(orderId);
-        entity.setOrderStatus(7);
-        this.updateById(entity);
-
-        WorkOrderLog logEntry = new WorkOrderLog();
-        logEntry.setOrderId(orderId);
-        logEntry.setOperType(7);
-        logEntry.setOperDesc("取消工单");
-        logEntry.setOperUserId(operUserId);
-        logEntry.setOperUserName(operUserName);
-        logEntry.setOperTime(new Date());
-        workOrderLogMapper.insert(logEntry);
-        return true;
+        if (order == null) {
+            throw new ServiceException("工单不存在或已被删除");
+        }
+        return order;
     }
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public boolean rejectOrder(Long orderId, String reason, Long operUserId, String operUserName) {
-        WorkOrder order = this.getById(orderId);
-        if (order == null) throw new ServiceException("工单不存在");
-        // 仅待派单/待接单可驳回,已流转的工单请使用取消
-        if (!Arrays.asList(1, 2).contains(order.getOrderStatus())) throw new ServiceException("当前工单状态不允许驳回");
-
-        WorkOrder entity = new WorkOrder();
-        entity.setOrderId(orderId);
-        entity.setOrderStatus(7);
-        this.updateById(entity);
+    private Long requireOrderId(Long orderId) {
+        if (orderId == null) {
+            throw new ServiceException("工单ID不能为空");
+        }
+        return orderId;
+    }
 
+    /**
+     * 写入工单操作日志(操作人取当前登录用户)
+     */
+    private void writeLog(Long orderId, WorkOrderLogOperTypeEnum operType, String operDesc, String attachUrl) {
         WorkOrderLog logEntry = new WorkOrderLog();
         logEntry.setOrderId(orderId);
-        logEntry.setOperType(7);
-        logEntry.setOperDesc("驳回工单,原因:" + (reason != null ? reason : ""));
-        logEntry.setOperUserId(operUserId);
-        logEntry.setOperUserName(operUserName);
+        logEntry.setOperType(operType.getCode());
+        logEntry.setOperDesc(operDesc);
+        logEntry.setAttachUrl(attachUrl);
+        logEntry.setOperUserId(currentUserId());
+        logEntry.setOperUserName(currentUserName());
         logEntry.setOperTime(new Date());
         workOrderLogMapper.insert(logEntry);
-        return true;
     }
 
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public boolean closeOrder(Long orderId, String result, Long operUserId, String operUserName) {
-        WorkOrder order = this.getById(orderId);
-        if (order == null) throw new ServiceException("工单不存在");
-        if (order.getOrderStatus() != 5) throw new ServiceException("当前工单状态不允许验收通过");
-
-        WorkOrder entity = new WorkOrder();
-        entity.setOrderId(orderId);
-        entity.setOrderStatus(6);
-        entity.setRealFinishTime(new Date());
-        this.updateById(entity);
+    /**
+     * 校验同一设备是否已存在未办结工单
+     */
+    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("该设备已有处理中的工单,需结案或取消后才能创建新工单");
+        }
+    }
 
-        // 工单已结案 → 同步隐患状态为"已整改"
-        syncHazardStatus(order, "已整改", result);
+    /**
+     * 生成全局唯一工单编号:WO + 14位时间戳 + 4位随机数
+     */
+    private String generateOrderNo() {
+        String pattern = "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) {
+                return no;
+            }
+        }
+        throw new ServiceException("工单编号生成失败,请重试");
+    }
 
-        // 设备报警状态由实时监测链路维护;工单结案不直接清除报警,避免覆盖仍存在的实时告警。
+    /**
+     * 隐患等级映射工单优先级:0-重大→紧急 3-轻微→低 其余→一般
+     */
+    private WorkOrderLevelEnum mapHazardLevelToOrderLevel(String hazardLevel) {
+        if ("0".equals(hazardLevel)) {
+            return WorkOrderLevelEnum.URGENT;
+        }
+        if ("3".equals(hazardLevel)) {
+            return WorkOrderLevelEnum.LOW;
+        }
+        return WorkOrderLevelEnum.NORMAL;
+    }
 
-        WorkOrderLog logEntry = new WorkOrderLog();
-        logEntry.setOrderId(orderId);
-        logEntry.setOperType(6);
-        logEntry.setOperDesc("验收通过,结果:" + result);
-        logEntry.setOperUserId(operUserId);
-        logEntry.setOperUserName(operUserName);
-        logEntry.setOperTime(new Date());
-        workOrderLogMapper.insert(logEntry);
-        return true;
+    private String resolveDeviceCode(String deviceId) {
+        try {
+            EquipmentBase equipment = equipmentBaseMapper.selectById(deviceId);
+            return equipment == null ? null : equipment.getEquipmentCode();
+        } catch (Exception e) {
+            log.warn("查询设备编码失败,deviceId={}", deviceId, e);
+            return null;
+        }
     }
 
     /**
      * 同步隐患状态:根据工单处理结果更新关联隐患记录的状态和整改反馈
-     * @param order 工单对象
-     * @param hazardStatus 隐患状态:整改中/已整改
-     * @param rectifyFeedback 整改反馈(说明/方案)
      */
     private void syncHazardStatus(WorkOrder order, String hazardStatus, String rectifyFeedback) {
         if (StringUtils.isEmpty(order.getHazardNo())) {
             return;
         }
         try {
-            LambdaQueryWrapper<HazardHiddenAccount> wrapper = new LambdaQueryWrapper<>();
-            wrapper.eq(HazardHiddenAccount::getHazardNo, order.getHazardNo());
-            HazardHiddenAccount hazard = hazardHiddenAccountMapper.selectOne(wrapper);
+            HazardHiddenAccount hazard = hazardHiddenAccountMapper.selectOne(new LambdaQueryWrapper<HazardHiddenAccount>().eq(HazardHiddenAccount::getHazardNo, order.getHazardNo()));
             if (hazard != null) {
                 HazardHiddenAccount update = new HazardHiddenAccount();
                 update.setHazardId(hazard.getHazardId());
@@ -528,12 +666,124 @@ public class BusinessWorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, W
                     update.setRectifyFeedback(rectifyFeedback);
                 }
                 hazardHiddenAccountMapper.updateById(update);
-                log.info("工单[{}]关联隐患[{}]状态同步为:{}, 整改反馈:{}",
-                        order.getOrderNo(), order.getHazardNo(), hazardStatus, rectifyFeedback);
+                log.info("工单[{}]关联隐患[{}]状态同步为:{}", order.getOrderNo(), order.getHazardNo(), hazardStatus);
             }
         } catch (Exception e) {
             log.error("工单[{}]同步隐患状态失败", order.getOrderNo(), e);
         }
     }
 
+    /**
+     * 批量回填责任人/派单人姓名,避免逐条查库
+     */
+    private void fillUserName(List<WorkOrder> records) {
+        if (CollectionUtils.isEmpty(records)) {
+            return;
+        }
+        Map<Long, String> userMap = loadUserMap(records);
+        for (WorkOrder record : records) {
+            if (record.getReceiveUser() != null) {
+                record.setReceiveUserName(userMap.getOrDefault(record.getReceiveUser(), String.valueOf(record.getReceiveUser())));
+            }
+            if (record.getDispatchUser() != null) {
+                record.setDispatchUserName(userMap.getOrDefault(record.getDispatchUser(), String.valueOf(record.getDispatchUser())));
+            }
+        }
+    }
+
+    /**
+     * 按本页实际涉及的用户ID批量查询并构建「用户ID → 姓名」映射。
+     *
+     * <p>此前是全量拉取用户表(selectAllEnabledUser)再建 Map,用户量大时每次分页查询都要扫描全表。
+     * 实际只用到当页记录的 receiveUser / dispatchUser,故改为按 ID 精确查询。
+     *
+     * @param records 当前页工单记录
+     * @return 用户ID → 姓名的映射;查询失败时返回空 Map(降级为展示ID,不影响主流程)
+     */
+    private Map<Long, String> loadUserMap(List<WorkOrder> records) {
+        Set<Long> userIds = new HashSet<>();
+        for (WorkOrder record : records) {
+            if (record.getReceiveUser() != null) {
+                userIds.add(record.getReceiveUser());
+            }
+            if (record.getDispatchUser() != null) {
+                userIds.add(record.getDispatchUser());
+            }
+        }
+        if (userIds.isEmpty()) {
+            return new HashMap<>(0);
+        }
+        try {
+            List<SysUser> users = sysUserService.selectEnabledUsersByIds(userIds);
+            if (CollectionUtils.isEmpty(users)) {
+                return new HashMap<>(0);
+            }
+            Map<Long, String> userMap = new HashMap<>(users.size() * 2);
+            for (SysUser user : users) {
+                if (user.getUserId() != null) {
+                    userMap.put(user.getUserId(), userLabel(user));
+                }
+            }
+            return userMap;
+        } catch (Exception e) {
+            log.warn("加载工单责任人用户映射失败", e);
+            return new HashMap<>(0);
+        }
+    }
+
+    private String userLabel(SysUser user) {
+        if (user == null) {
+            return "";
+        }
+        return StringUtils.isNotEmpty(user.getNickName()) ? user.getNickName() : user.getUserName();
+    }
+
+    private Long currentUserId() {
+        return assignScopeSupport.currentUserId();
+    }
+
+    /**
+     * 获取当前登录用户姓名:用户昵称(nickName)优先,缺省时回退到登录账号。
+     * 工单操作日志展示姓名比账号更直观,与 WaterSupplyAlarmServiceImpl 保持一致。
+     */
+    private String currentUserName() {
+        try {
+            LoginUser loginUser = SecurityUtils.getLoginUser();
+            if (loginUser == null) {
+                return null;
+            }
+            if (loginUser.getUser() != null && StringUtils.isNotEmpty(loginUser.getUser().getNickName())) {
+                return loginUser.getUser().getNickName();
+            }
+            return loginUser.getUsername();
+        } catch (Exception e) {
+            log.warn("获取当前登录用户姓名失败", e);
+            return null;
+        }
+    }
+
+    private List<Integer> parseStatusList(String orderStatus) {
+        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);
+        return prefixEmpty ? detail : (detailEmpty ? prefix : prefix + ":" + detail);
+    }
+
+    private List<String> splitAttach(String attachUrl) {
+        if (StringUtils.isEmpty(attachUrl)) {
+            return Collections.emptyList();
+        }
+        return Arrays.stream(attachUrl.split(",")).map(String::trim).filter(StringUtils::isNotEmpty).collect(Collectors.toList());
+    }
 }

+ 13 - 17
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/WaterSupplyDeviceServiceImpl.java

@@ -18,6 +18,8 @@ import com.zksy.base.domain.WorkOrder;
 import com.zksy.base.domain.vo.*;
 import com.zksy.base.event.DeviceStatusChangePublisher;
 import com.zksy.base.environment.domain.ERealTimeData;
+import com.zksy.base.enums.WorkOrderStatusEnum;
+import com.zksy.ops.dto.WorkOrderDispatchDTO;
 import com.zksy.base.environment.mapper.ERealTimeDataMapper;
 import com.zksy.base.gas.domain.GasMonitorData;
 import com.zksy.base.gas.mapper.GasMonitorDataMapper;
@@ -451,8 +453,11 @@ public class WaterSupplyDeviceServiceImpl extends ServiceImpl<EquipmentBaseMappe
         if (vo.getReceiveUserId() == null) {
             return true;
         }
-        return businessWorkOrderService.dispatchOrder(workOrder.getOrderId(), vo.getReceiveUserId(),
-                vo.getMaintainRemark(), currentUserIdOrNull(), currentUserNameOrNull());
+        WorkOrderDispatchDTO dispatchDTO = new WorkOrderDispatchDTO();
+        dispatchDTO.setOrderId(workOrder.getOrderId());
+        dispatchDTO.setHandlerId(vo.getReceiveUserId());
+        dispatchDTO.setRemarks(vo.getMaintainRemark());
+        return businessWorkOrderService.dispatch(dispatchDTO);
     }
 
     private Long currentUserIdOrNull() {
@@ -487,10 +492,9 @@ public class WaterSupplyDeviceServiceImpl extends ServiceImpl<EquipmentBaseMappe
         WorkOrder workOrder = new WorkOrder();
         workOrder.setDeviceId(device.getEquipmentId());
         workOrder.setDeviceCode(device.getEquipmentCode());
-        workOrder.setOrderType(1);
-        workOrder.setOrderLevel(vo.getOrderLevel() == null ? 2 : vo.getOrderLevel());
+        workOrder.setOrderLevel(vo.getOrderLevel() == null ? 1 : vo.getOrderLevel());
         workOrder.setOrderDesc(StrUtil.isNotBlank(vo.getFaultDescription()) ? vo.getFaultDescription() : vo.getMaintainContent());
-        Long orderId = businessWorkOrderService.createWorkOrder(workOrder);
+        Long orderId = businessWorkOrderService.addWorkOrder(workOrder);
         workOrder.setOrderId(orderId);
         return workOrder;
     }
@@ -930,15 +934,12 @@ public class WaterSupplyDeviceServiceImpl extends ServiceImpl<EquipmentBaseMappe
         List<WorkOrder> orders = workOrderMapper.selectList(
                 new LambdaQueryWrapper<WorkOrder>()
                         .in(WorkOrder::getDeviceId, equipmentIds)
-                        .eq(WorkOrder::getOrderType, 1)
-                        .notIn(WorkOrder::getOrderStatus, 6, 7)
+                        .notIn(WorkOrder::getOrderStatus, WorkOrderStatusEnum.finishedCodes())
                         .orderByDesc(WorkOrder::getCreateTime)
         );
         Map<String, WorkOrder> result = new HashMap<>();
         for (WorkOrder order : orders) {
-            if (!Objects.equals(order.getOrderType(), 1)
-                    || Objects.equals(order.getOrderStatus(), 6)
-                    || Objects.equals(order.getOrderStatus(), 7)) {
+            if (WorkOrderStatusEnum.isFinished(order.getOrderStatus())) {
                 continue;
             }
             result.putIfAbsent(order.getDeviceId(), order);
@@ -990,15 +991,10 @@ public class WaterSupplyDeviceServiceImpl extends ServiceImpl<EquipmentBaseMappe
     }
 
     private String resolveWorkOrderPerson(WorkOrder workOrder) {
-        if (workOrder == null || StrUtil.isBlank(workOrder.getReceiveUser())) {
+        if (workOrder == null || workOrder.getReceiveUser() == null) {
             return null;
         }
-        return Arrays.stream(workOrder.getReceiveUser().split(","))
-                .map(String::trim)
-                .filter(StrUtil::isNotBlank)
-                .map(this::resolveUserName)
-                .filter(StrUtil::isNotBlank)
-                .collect(Collectors.joining("、"));
+        return resolveUserName(String.valueOf(workOrder.getReceiveUser()));
     }
 
     private String resolveUserName(String userId) {

+ 77 - 40
pipe-network-service/zksy-system/src/main/java/com/zksy/base/service/impl/WorkOrderServiceImpl.java

@@ -8,6 +8,9 @@ import com.zksy.base.domain.CaseInfo;
 import com.zksy.base.domain.EquipmentBase;
 import com.zksy.base.domain.WorkOrder;
 import com.zksy.base.domain.WorkOrderLog;
+import com.zksy.base.enums.WorkOrderDelayApplyStatusEnum;
+import com.zksy.base.enums.WorkOrderLogOperTypeEnum;
+import com.zksy.base.enums.WorkOrderStatusEnum;
 import com.zksy.base.warning.domain.EarlyWarning;
 import com.zksy.base.warning.service.EarlyWarningService;
 import com.zksy.base.mapper.CaseInfoMapper;
@@ -116,20 +119,24 @@ public class WorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder
     public Long addWorkOrder(WorkOrderAddInDTO inDTO) {
         log.info("新增工单-入参:{}", inDTO);
         resolveWarningDevice(inDTO);
-        // 同一设备存在进行中工单(状态非 6 已结案/7 已驳回)时禁止重复建单
+        // 同一设备存在未办结工单(非 已结案/已取消)时禁止重复建单
         if (inDTO.getDeviceId() != null) {
             Long count = this.lambdaQuery()
                     .eq(WorkOrder::getDeviceId, inDTO.getDeviceId())
-                    .notIn(WorkOrder::getOrderStatus, 6, 7)
+                    .notIn(WorkOrder::getOrderStatus, WorkOrderStatusEnum.finishedCodes())
                     .count();
             if (count > 0) {
-                throw new ServiceException("该设备已有处理中的工单,需结案或驳回后才能创建新工单");
+                throw new ServiceException("该设备已有处理中的工单,需结案或取消后才能创建新工单");
             }
         }
         WorkOrder workOrder = BeanUtil.copyProperties(inDTO, WorkOrder.class);
         workOrder.setOrderNo(generateOrderNo());
-        // 初始状态:1-待派单
-        workOrder.setOrderStatus(1);
+        // 初始状态:待派单
+        workOrder.setOrderStatus(WorkOrderStatusEnum.PENDING_DISPATCH.getCode());
+        workOrder.setDelayApplyStatus(WorkOrderDelayApplyStatusEnum.NONE.getCode());
+        if (workOrder.getFaultTime() == null) {
+            workOrder.setFaultTime(new Date());
+        }
         workOrder.setCreateTime(new Date());
         workOrder.setUpdateTime(new Date());
         if(inDTO.getCaseId() != null){
@@ -154,8 +161,8 @@ public class WorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder
         this.save(workOrder);
         WorkOrderLog workOrderLog = new WorkOrderLog();
         workOrderLog.setOrderId(workOrder.getOrderId());
-        //1-创建工单
-        workOrderLog.setOperType(1);
+        // 创建工单
+        workOrderLog.setOperType(WorkOrderLogOperTypeEnum.CREATE.getCode());
         workOrderLog.setOperDesc(inDTO.getOrderDesc() + ",创建工单");
         LoginUser loginUser = SecurityUtils.getLoginUser();
         if (loginUser != null) {
@@ -163,6 +170,7 @@ public class WorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder
             workOrderLog.setOperUserName(loginUser.getUser().getNickName());
         }
         workOrderLogMapper.insert(workOrderLog);
+
         // 返回工单ID,供后续派单使用
         return workOrder.getOrderId();
     }
@@ -218,16 +226,16 @@ public class WorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder
             return emptyPage(inDTO.getPageNum(), inDTO.getPageSize());
         }
         Page<WorkOrder> page = new Page<>(inDTO.getPageNum(), inDTO.getPageSize());
-        // 经办工单状态:2-待接单 3-已接单 4-处理中 5-待验收 8-延期审核
-        List<Integer> processingStatusList = Arrays.asList(2, 3, 4, 5, 8);
+        // 经办工单状态:待接单/已接单/处理中/待验收/延期中
+        List<Integer> processingStatusList = WorkOrderStatusEnum.activeCodes();
         LambdaQueryWrapper<WorkOrder> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.in(WorkOrder::getDeviceId, manholeDeviceIds)
                 .in(WorkOrder::getOrderStatus, processingStatusList)
                 .like(StringUtils.isNotEmpty(inDTO.getOrderNo()), WorkOrder::getOrderNo, inDTO.getOrderNo())
                 .like(StringUtils.isNotEmpty(inDTO.getCaseNo()), WorkOrder::getCaseNo, inDTO.getCaseNo())
-                .eq(inDTO.getOrderType() != null, WorkOrder::getOrderType, inDTO.getOrderType())
                 .eq(inDTO.getOrderLevel() != null, WorkOrder::getOrderLevel, inDTO.getOrderLevel())
-                .eq(inDTO.getReceiveUser() != null, WorkOrder::getReceiveUser, inDTO.getReceiveUser())
+                .eq(inDTO.getReceiveUser() != null, WorkOrder::getReceiveUser,
+                        inDTO.getReceiveUser() == null ? null : Long.valueOf(inDTO.getReceiveUser().toString()))
                 .orderByDesc(WorkOrder::getUpdateTime);
         Page<WorkOrder> rs = this.page(page, queryWrapper);
         List<WorkOrderOutDTO> outDTOList = BeanUtil.copyToList(rs.getRecords(), WorkOrderOutDTO.class);
@@ -249,14 +257,13 @@ public class WorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder
             return emptyPage(inDTO.getPageNum(), inDTO.getPageSize());
         }
         Page<WorkOrder> page = new Page<>(inDTO.getPageNum(), inDTO.getPageSize());
-        // 办结工单状态:6-已结案 7-已驳回 9-已延期
-        List<Integer> finishedStatusList = Arrays.asList(6, 7, 9);
+        // 办结工单状态:已结案、已取消
+        List<Integer> finishedStatusList = WorkOrderStatusEnum.finishedCodes();
         LambdaQueryWrapper<WorkOrder> queryWrapper = new LambdaQueryWrapper<>();
         queryWrapper.in(WorkOrder::getDeviceId, manholeDeviceIds)
                 .in(WorkOrder::getOrderStatus, finishedStatusList)
                 .like(StringUtils.isNotEmpty(inDTO.getOrderNo()), WorkOrder::getOrderNo, inDTO.getOrderNo())
                 .like(StringUtils.isNotEmpty(inDTO.getCaseNo()), WorkOrder::getCaseNo, inDTO.getCaseNo())
-                .eq(inDTO.getOrderType() != null, WorkOrder::getOrderType, inDTO.getOrderType())
                 .eq(inDTO.getOrderLevel() != null, WorkOrder::getOrderLevel, inDTO.getOrderLevel())
                 .eq(inDTO.getVerifyUser() != null, WorkOrder::getVerifyUser, inDTO.getVerifyUser())
                 .ge(inDTO.getStartTime() != null, WorkOrder::getRealFinishTime, inDTO.getStartTime())
@@ -290,32 +297,37 @@ public class WorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder
         }
         WorkOrder entity = new WorkOrder();
         entity.setOrderId(inDTO.getOrderId());
-        if(inDTO.getOperType() == 2){
-            //2-派单
-            entity.setOrderStatus(2);
+        WorkOrderLogOperTypeEnum operTypeEnum = WorkOrderLogOperTypeEnum.getByCode(inDTO.getOperType());
+        if (operTypeEnum == null || operTypeEnum == WorkOrderLogOperTypeEnum.CREATE) {
+            throw new ServiceException("操作类型不合法");
+        }
+        if (operTypeEnum == WorkOrderLogOperTypeEnum.DISPATCH){
+            // 派单:待派单 → 待接单
+            entity.setOrderStatus(WorkOrderStatusEnum.PENDING_ACCEPT.getCode());
             entity.setDispatchUser(inDTO.getOperUserId());
             entity.setDispatchTime(new Date());
-            entity.setReceiveUser(inDTO.getReceiveUser().toString());
-        } else if(inDTO.getOperType() == 3){
-            //3-接单
-            entity.setOrderStatus(3);
-            entity.setReceiveUser(inDTO.getOperUserId().toString());
-        } else if(inDTO.getOperType() == 4){
-            //4-开始维修
-            entity.setOrderStatus(4);
+            entity.setReceiveUser(longValue(inDTO.getReceiveUser()));
+        } else if(operTypeEnum == WorkOrderLogOperTypeEnum.ACCEPT){
+            // 接单:待接单 → 已接单
+            entity.setOrderStatus(WorkOrderStatusEnum.ACCEPTED.getCode());
+            entity.setReceiveUser(inDTO.getOperUserId());
+        } else if(operTypeEnum == WorkOrderLogOperTypeEnum.PROCESS){
+            // 处置:→ 处理中
+            entity.setOrderStatus(WorkOrderStatusEnum.PROCESSING.getCode());
             if (StringUtils.isNotBlank(inDTO.getMaintenanceBeforeImg())) {
                 entity.setMaintenanceBeforeImg(inDTO.getMaintenanceBeforeImg());
             }
-        } else if(inDTO.getOperType() == 5){
-            //5-提交验收
-            entity.setOrderStatus(5);
+        } else if(operTypeEnum == WorkOrderLogOperTypeEnum.SUBMIT_VERIFY){
+            // 提交验收:→ 待验收
+            entity.setOrderStatus(WorkOrderStatusEnum.PENDING_VERIFY.getCode());
             entity.setVerifyUser(workOrder.getDispatchUser());
             if (StringUtils.isNotBlank(inDTO.getMaintenanceAfterImg())) {
                 entity.setMaintenanceAfterImg(inDTO.getMaintenanceAfterImg());
             }
-        } else if(inDTO.getOperType() == 6){
-            //6-验收通过
-            entity.setOrderStatus(6);
+        } else if(operTypeEnum == WorkOrderLogOperTypeEnum.VERIFY_PASS){
+            // 验收通过:→ 已结案
+            entity.setOrderStatus(WorkOrderStatusEnum.CLOSED.getCode());
+            entity.setRealFinishTime(new Date());
             entity.setVerifyUser(inDTO.getOperUserId());
             entity.setVerifyTime(new Date());
             // 同步将案件状态改为已办结
@@ -344,23 +356,48 @@ public class WorkOrderServiceImpl extends ServiceImpl<WorkOrderMapper, WorkOrder
                             workOrder.getOrderNo(), workOrder.getAlarmId(), e);
                 }
             }
-        } else if(inDTO.getOperType() == 7){
-            //7-驳回工单
-            entity.setOrderStatus(7);
-        } else if(inDTO.getOperType() == 8){
-            //8-申请延期
-            entity.setOrderStatus(8);
+        } else if(operTypeEnum == WorkOrderLogOperTypeEnum.VERIFY_REJECT){
+            // 验收驳回:→ 退回处理中
+            entity.setOrderStatus(WorkOrderStatusEnum.PROCESSING.getCode());
+            entity.setVerifyUser(inDTO.getOperUserId());
+            entity.setVerifyTime(new Date());
+        } else if(operTypeEnum == WorkOrderLogOperTypeEnum.CANCEL){
+            // 取消工单:→ 已取消
+            entity.setOrderStatus(WorkOrderStatusEnum.CANCELLED.getCode());
+        } else if(operTypeEnum == WorkOrderLogOperTypeEnum.DELAY_APPLY){
+            // 申请延期:延期申请状态 → 待延期审核
+            entity.setDelayApplyStatus(WorkOrderDelayApplyStatusEnum.PENDING_AUDIT.getCode());
             entity.setDelayedReason(inDTO.getDelayedReason());
             entity.setDelayedDeadline(inDTO.getDelayedDeadline());
-        } else if(inDTO.getOperType() == 9){
-            //9-延期审核通过
-            entity.setOrderStatus(9);
+        } else if(operTypeEnum == WorkOrderLogOperTypeEnum.DELAY_APPROVE){
+            // 延期审核通过:→ 延期中
+            entity.setDelayApplyStatus(WorkOrderDelayApplyStatusEnum.APPROVED.getCode());
+            entity.setOrderStatus(WorkOrderStatusEnum.DELAYING.getCode());
+        } else if(operTypeEnum == WorkOrderLogOperTypeEnum.DELAY_REJECT){
+            // 延期审核驳回
+            entity.setDelayApplyStatus(WorkOrderDelayApplyStatusEnum.REJECTED.getCode());
         }
         WorkOrderLog workOrderLog = BeanUtil.copyProperties(inDTO, WorkOrderLog.class);
+        workOrderLog.setAttachUrl(inDTO.getMaintenanceBeforeImg() != null
+                ? inDTO.getMaintenanceBeforeImg() : inDTO.getMaintenanceAfterImg());
         workOrderLog.setOperUserName(user.getNickName());
         workOrderLogMapper.insert(workOrderLog);
         return this.updateById(entity);
     }
+
+    /**
+     * 责任人入参兼容 Long / String,统一转换为 Long
+     */
+    private Long longValue(Object value) {
+        if (value == null) {
+            return null;
+        }
+        if (value instanceof Number) {
+            return ((Number) value).longValue();
+        }
+        String text = value.toString().trim();
+        return text.isEmpty() ? null : Long.valueOf(text);
+    }
 }
 
 

+ 8 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/system/mapper/SysUserMapper.java

@@ -26,6 +26,14 @@ public interface SysUserMapper
      */
     public List<SysUser> selectAllEnabledUser();
 
+    /**
+     * 按用户ID批量查询有效用户(未删除且状态正常),用于按需回填姓名,避免全量拉取用户表
+     *
+     * @param userIds 用户ID集合
+     * @return 用户信息集合
+     */
+    public List<SysUser> selectEnabledUsersByIds(@Param("userIds") java.util.Collection<Long> userIds);
+
     /**
      * 根据条件分页查询已配用户角色列表
      * 

+ 8 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/system/service/ISysUserService.java

@@ -25,6 +25,14 @@ public interface ISysUserService
      */
     public List<SysUser> selectAllEnabledUser();
 
+    /**
+     * 按用户ID批量查询有效用户(未删除且状态正常)
+     *
+     * @param userIds 用户ID集合,为空时返回空集合
+     * @return 用户信息集合
+     */
+    public List<SysUser> selectEnabledUsersByIds(java.util.Collection<Long> userIds);
+
     /**
      * 根据条件分页查询已分配用户角色列表
      * 

+ 16 - 0
pipe-network-service/zksy-system/src/main/java/com/zksy/system/service/impl/SysUserServiceImpl.java

@@ -89,6 +89,22 @@ public class SysUserServiceImpl implements ISysUserService
         return userMapper.selectAllEnabledUser();
     }
 
+    /**
+     * 按用户ID批量查询有效用户(未删除且状态正常,不做数据权限过滤)
+     *
+     * @param userIds 用户ID集合,为空时返回空集合
+     * @return 用户信息集合
+     */
+    @Override
+    public List<SysUser> selectEnabledUsersByIds(java.util.Collection<Long> userIds)
+    {
+        if (userIds == null || userIds.isEmpty())
+        {
+            return new java.util.ArrayList<>();
+        }
+        return userMapper.selectEnabledUsersByIds(userIds);
+    }
+
     /**
      * 根据条件分页查询已分配用户角色列表
      * 

+ 9 - 7
pipe-network-service/zksy-system/src/main/resources/mapper/WorkOrderMapper.xml

@@ -12,14 +12,15 @@
             <result property="caseNo" column="case_no" jdbcType="VARCHAR"/>
             <result property="deviceId" column="device_id" jdbcType="VARCHAR"/>
             <result property="deviceCode" column="device_code" jdbcType="VARCHAR"/>
-            <result property="orderType" column="order_type" jdbcType="TINYINT"/>
             <result property="orderLevel" column="order_level" jdbcType="TINYINT"/>
             <result property="orderDesc" column="order_desc" jdbcType="VARCHAR"/>
             <result property="deptId" column="dept_id" jdbcType="BIGINT"/>
             <result property="dispatchUser" column="dispatch_user" jdbcType="BIGINT"/>
             <result property="dispatchTime" column="dispatch_time" jdbcType="TIMESTAMP"/>
-            <result property="receiveUser" column="receive_user" jdbcType="VARCHAR"/>
+            <result property="receiveUser" column="receive_user" jdbcType="BIGINT"/>
             <result property="orderStatus" column="order_status" jdbcType="TINYINT"/>
+            <result property="delayApplyStatus" column="delay_apply_status" jdbcType="TINYINT"/>
+            <result property="faultTime" column="fault_time" jdbcType="TIMESTAMP"/>
             <result property="delayedReason" column="delayed_reason" jdbcType="VARCHAR"/>
             <result property="delayedDeadline" column="delayed_deadline" jdbcType="TIMESTAMP"/>
             <result property="planFinishTime" column="plan_finish_time" jdbcType="TIMESTAMP"/>
@@ -34,23 +35,24 @@
     </resultMap>
 
     <sql id="Base_Column_List">
-        order_id,order_no,alarm_id,
+        order_id,order_no,alarm_id,hazard_id,hazard_no,
         case_id,case_no,device_id,
-        device_code,order_type,order_level,
+        device_code,order_level,fault_time,
         order_desc,dept_id,dispatch_user,
         dispatch_time,receive_user,order_status,
-        delayed_reason,delayed_deadline,plan_finish_time,
+        delay_apply_status,delayed_reason,delayed_deadline,plan_finish_time,
         real_finish_time,repair_result,maintenance_before_img,maintenance_after_img,verify_user,
         verify_time,create_time,update_time
     </sql>
 
+    <!-- 待派单(0)/待接单(1) 始终提醒;其余未办结状态仅在超时后提醒 -->
     <select id="selectPendingOrOverdue" resultMap="BaseResultMap">
         SELECT
         <include refid="Base_Column_List"/>
         FROM app_user.work_order
-        WHERE order_status IN (1, 2)
+        WHERE order_status IN (0, 1)
            OR (
-                order_status IN (3, 4, 5, 8, 9)
+                order_status IN (2, 3, 4, 6)
                 AND COALESCE(delayed_deadline, plan_finish_time) &lt; #{now}
            )
         ORDER BY order_level ASC NULLS LAST,

+ 10 - 0
pipe-network-service/zksy-system/src/main/resources/mapper/system/SysUserMapper.xml

@@ -96,6 +96,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
         order by u.nick_name
     </select>
 
+    <select id="selectEnabledUsersByIds" resultMap="SysUserResult">
+        select u.user_id, u.dept_id, u.nick_name, u.user_name
+        from app_user.sys_user u
+        where u.del_flag = '0' and u.status = '0'
+        and u.user_id in
+        <foreach collection="userIds" item="userId" open="(" separator="," close=")">
+            #{userId}
+        </foreach>
+    </select>
+
     <select id="selectAllocatedList" parameterType="SysUser" resultMap="SysUserResult">
 	    select distinct u.user_id, u.dept_id, u.user_name, u.nick_name, u.email, u.phonenumber, u.status, u.create_time
 	    from app_user.sys_user u