Browse Source

feat(repair): 添加委外单功能模块

- 新增委外单实体类、DTO、VO及Mapper接口
- 实现委外单服务层逻辑,包括创建、完成、查询等功能
- 添加委外单控制器,提供REST API接口
- 扩展工单状态枚举,增加委外维修中状态
- 集成委外单号生成工具方法
- 添加委外单状态校验机制
- 实现委外单分页查询及权限过滤功能
- 添加委外单完成图片和附件管理
- 更新系统文件类型枚举,增加委外相关文件类型
- 提供委外单数据库表结构及字典数据初始化SQL
dazhaxie 1 tuần trước cách đây
mục cha
commit
c8b683dee8

+ 28 - 0
db/委外单建表SQL.sql

@@ -0,0 +1,28 @@
+-- 委外单建表SQL
+CREATE TABLE IF NOT EXISTS t_repair_entrust (
+  id VARCHAR(64) PRIMARY KEY COMMENT '主键',
+  repair_id VARCHAR(64) NOT NULL COMMENT '关联工单ID',
+  entrust_no VARCHAR(64) COMMENT '委外单号',
+  out_type INT COMMENT '委外类别(字典REPAIR_OUT_TYPE)',
+  entrust_user_id VARCHAR(64) COMMENT '委外申请人ID(即工单维修人)',
+  repair_user_id VARCHAR(64) NOT NULL COMMENT '指定委外维修人ID',
+  reason VARCHAR(500) COMMENT '委外原因',
+  status INT DEFAULT 1 COMMENT '状态:1-委外维修中 2-已完成',
+  finish_remark VARCHAR(1000) COMMENT '完成-维修情况(必填)',
+  finish_time DATETIME COMMENT '完成时间',
+  created_time DATETIME COMMENT '创建时间',
+  update_time DATETIME COMMENT '更新时间',
+  created_user_id VARCHAR(64) COMMENT '创建人',
+  update_user_id VARCHAR(64) COMMENT '更新人'
+) COMMENT='委外单';
+
+-- 在 REPAIR_APPLICATION_FORM_STATUS 字典中新增 委外维修中 状态
+INSERT INTO `t_sys_dict`(`id`, `type`, `type_name`, `code`, `value`, `value_type`, `label`, `description`, `sort`, `created_time`, `update_time`, `del_flag`, `created_user_id`, `update_user_id`, `created_user_name`, `update_user_name`, `child_flag`, `parent_id`)
+VALUES ('repair_status_entrust_9', 'REPAIR_APPLICATION_FORM_STATUS', '工单状态', 'REPAIR_APPLICATION_FORM_STATUS_ENTRUST_REPAIR', '9', 2, '委外维修中', '委外维修中', 9, NOW(), NOW(), '0', '1', '1', '系统管理员', '系统管理员', NULL, NULL);
+
+-- 新增委外单状态字典
+INSERT INTO `t_sys_dict`(`id`, `type`, `type_name`, `code`, `value`, `value_type`, `label`, `description`, `sort`, `created_time`, `update_time`, `del_flag`, `created_user_id`, `update_user_id`, `created_user_name`, `update_user_name`, `child_flag`, `parent_id`)
+VALUES ('repair_entrust_status_1', 'REPAIR_ENTRUST_STATUS', '委外单状态', 'REPAIR_ENTRUST_STATUS_PROCESSING', '1', 2, '委外维修中', '委外维修中', 1, NOW(), NOW(), '0', '1', '1', '系统管理员', '系统管理员', NULL, NULL);
+
+INSERT INTO `t_sys_dict`(`id`, `type`, `type_name`, `code`, `value`, `value_type`, `label`, `description`, `sort`, `created_time`, `update_time`, `del_flag`, `created_user_id`, `update_user_id`, `created_user_name`, `update_user_name`, `child_flag`, `parent_id`)
+VALUES ('repair_entrust_status_2', 'REPAIR_ENTRUST_STATUS', '委外单状态', 'REPAIR_ENTRUST_STATUS_FINISHED', '2', 2, '已完成', '已完成', 2, NOW(), NOW(), '0', '1', '1', '系统管理员', '系统管理员', NULL, NULL);

+ 10 - 0
platform-common/src/main/java/com/platform/common/util/IdGeneratorUtils.java

@@ -317,6 +317,16 @@ public class IdGeneratorUtils {
     }
 
     /**
+     * 生成委外单号
+     *
+     * @param countNum
+     * @return
+     */
+    public static String getEntrustNo(Integer countNum) {
+        return getProjectNum("WB", countNum);
+    }
+
+    /**
      * 生成采购计划编号
      *
      * @param countNum

+ 94 - 0
platform-dao/src/main/java/com/platform/dao/dto/repair/RepairEntrustDTO.java

@@ -0,0 +1,94 @@
+package com.platform.dao.dto.repair;
+
+import com.platform.common.bean.BaseDTO;
+import com.platform.dao.entity.upms.SysFile;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
+
+import javax.persistence.Id;
+import javax.persistence.Transient;
+import javax.validation.constraints.NotBlank;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * @Description 委外单DTO
+ */
+@Data
+@Accessors(chain = true)
+@EqualsAndHashCode(callSuper = true)
+public class RepairEntrustDTO extends BaseDTO implements Serializable {
+
+    /**
+     * 主键
+     */
+    @Id
+    private String id;
+
+    /**
+     * 关联工单ID
+     */
+    @NotBlank(groups = {com.platform.common.validation.group.AddGroup.class}, message = "关联工单ID不能为空")
+    private String repairId;
+
+    /**
+     * 委外单号
+     */
+    private String entrustNo;
+
+    /**
+     * 委外类别
+     */
+    private Integer outType;
+
+    /**
+     * 委外申请人ID
+     */
+    private String entrustUserId;
+
+    /**
+     * 指定委外维修人ID
+     */
+    @NotBlank(groups = {com.platform.common.validation.group.AddGroup.class}, message = "委外维修人不能为空")
+    private String repairUserId;
+
+    /**
+     * 委外原因
+     */
+    private String reason;
+
+    /**
+     * 状态
+     */
+    private Integer status;
+
+    /**
+     * 完成-维修情况
+     */
+    private String finishRemark;
+
+    /**
+     * 完成时间
+     */
+    private LocalDateTime finishTime;
+
+    /**
+     * 完成图片列表
+     */
+    @Transient
+    private List<SysFile> finishFileList;
+
+    /**
+     * 完成附件列表
+     */
+    @Transient
+    private List<SysFile> finishAttachmentList;
+
+    /**
+     * 当前用户ID(用于权限过滤,不映射数据库)
+     */
+    @Transient
+    private String currentUserId;
+}

+ 134 - 0
platform-dao/src/main/java/com/platform/dao/entity/repair/RepairEntrust.java

@@ -0,0 +1,134 @@
+package com.platform.dao.entity.repair;
+
+import com.platform.dao.entity.upms.SysFile;
+import lombok.Data;
+
+import javax.persistence.Id;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * @Description 委外单实体类
+ */
+@Table(name = "t_repair_entrust")
+@Data
+public class RepairEntrust implements Serializable {
+
+    /**
+     * 主键
+     */
+    @Id
+    private String id;
+
+    /**
+     * 关联工单ID
+     */
+    private String repairId;
+
+    /**
+     * 委外单号
+     */
+    private String entrustNo;
+
+    /**
+     * 委外类别(字典REPAIR_OUT_TYPE)
+     */
+    private Integer outType;
+
+    /**
+     * 委外申请人ID(即工单维修人)
+     */
+    private String entrustUserId;
+
+    /**
+     * 指定委外维修人ID
+     */
+    private String repairUserId;
+
+    /**
+     * 委外原因
+     */
+    private String reason;
+
+    /**
+     * 状态:1-委外维修中 2-已完成
+     */
+    private Integer status;
+
+    /**
+     * 完成-维修情况(必填)
+     */
+    private String finishRemark;
+
+    /**
+     * 完成时间
+     */
+    private LocalDateTime finishTime;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createdTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建人
+     */
+    private String createdUserId;
+
+    /**
+     * 更新人
+     */
+    private String updateUserId;
+
+    // ========== Transient 关联字段 ==========
+
+    /**
+     * 委外维修人姓名
+     */
+    @Transient
+    private String repairUserName;
+
+    /**
+     * 委外申请人姓名
+     */
+    @Transient
+    private String entrustUserName;
+
+    /**
+     * 设备名称
+     */
+    @Transient
+    private String sbName;
+
+    /**
+     * 设备编号
+     */
+    @Transient
+    private String sbNo;
+
+    /**
+     * 工单号
+     */
+    @Transient
+    private String repairNo;
+
+    /**
+     * 完成图片列表
+     */
+    @Transient
+    private List<SysFile> finishFileList;
+
+    /**
+     * 完成附件列表
+     */
+    @Transient
+    private List<SysFile> finishAttachmentList;
+}

+ 5 - 1
platform-dao/src/main/java/com/platform/dao/enums/RepairApplicationFormStatusEnum.java

@@ -40,6 +40,10 @@ public enum RepairApplicationFormStatusEnum {
     /**
      * 转派审批中
      */
-    TRANSFER_APPROVAL(8);
+    TRANSFER_APPROVAL(8),
+    /**
+     * 委外维修中
+     */
+    ENTRUST_REPAIR(9);
     private final Integer value;
 }

+ 21 - 0
platform-dao/src/main/java/com/platform/dao/enums/RepairEntrustStatusEnum.java

@@ -0,0 +1,21 @@
+package com.platform.dao.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * @Description 委外单状态枚举
+ */
+@Getter
+@AllArgsConstructor
+public enum RepairEntrustStatusEnum {
+    /**
+     * 委外维修中
+     */
+    PROCESSING(1),
+    /**
+     * 已完成
+     */
+    FINISHED(2);
+    private final Integer value;
+}

+ 3 - 1
platform-dao/src/main/java/com/platform/dao/enums/SysFileTypeEnum.java

@@ -37,7 +37,9 @@ public enum SysFileTypeEnum {
     Sb_MEASURE_FILES(32, "计量文件"),
     VERIFY_RECORD_IMAGES(54, "流程审批记录图片"),
     SB_AREA_IMAGES(56, "厂区车间图"),
-    VERIFY_RECORD_FILES(55, "流程审批记录附件"),;
+    VERIFY_RECORD_FILES(55, "流程审批记录附件"),
+    ENTRUST_FINISH_IMGS(25, "委外-完成图片"),
+    ENTRUST_FINISH_FILES(26, "委外-完成附件"),;
     private Integer value;
     private final String description;
 }

+ 29 - 0
platform-dao/src/main/java/com/platform/dao/mapper/repair/RepairEntrustMapper.java

@@ -0,0 +1,29 @@
+package com.platform.dao.mapper.repair;
+
+import com.platform.dao.config.MyMapper;
+import com.platform.dao.dto.repair.RepairEntrustDTO;
+import com.platform.dao.entity.repair.RepairEntrust;
+import com.platform.dao.vo.repair.RepairEntrustVO;
+
+import java.util.List;
+
+/**
+ * @Description 委外单 Mapper
+ */
+public interface RepairEntrustMapper extends MyMapper<RepairEntrust> {
+
+    /**
+     * 分页查询委外单列表
+     */
+    List<RepairEntrustVO> selectPageList(RepairEntrustDTO dto);
+
+    /**
+     * 根据工单ID查询委外单列表
+     */
+    List<RepairEntrustVO> selectByRepairId(String repairId);
+
+    /**
+     * 查询委外单详情(含关联字段)
+     */
+    RepairEntrustVO selectDetailById(String id);
+}

+ 124 - 0
platform-dao/src/main/java/com/platform/dao/vo/repair/RepairEntrustVO.java

@@ -0,0 +1,124 @@
+package com.platform.dao.vo.repair;
+
+import com.platform.dao.entity.upms.SysFile;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * @Description 委外单VO
+ */
+@Data
+@Accessors(chain = true)
+public class RepairEntrustVO implements Serializable {
+
+    /**
+     * 主键
+     */
+    private String id;
+
+    /**
+     * 关联工单ID
+     */
+    private String repairId;
+
+    /**
+     * 委外单号
+     */
+    private String entrustNo;
+
+    /**
+     * 委外类别
+     */
+    private Integer outType;
+
+    /**
+     * 委外申请人ID
+     */
+    private String entrustUserId;
+
+    /**
+     * 指定委外维修人ID
+     */
+    private String repairUserId;
+
+    /**
+     * 委外原因
+     */
+    private String reason;
+
+    /**
+     * 状态:1-委外维修中 2-已完成
+     */
+    private Integer status;
+
+    /**
+     * 完成-维修情况
+     */
+    private String finishRemark;
+
+    /**
+     * 完成时间
+     */
+    private LocalDateTime finishTime;
+
+    /**
+     * 创建时间
+     */
+    private LocalDateTime createdTime;
+
+    /**
+     * 更新时间
+     */
+    private LocalDateTime updateTime;
+
+    /**
+     * 创建人
+     */
+    private String createdUserId;
+
+    /**
+     * 更新人
+     */
+    private String updateUserId;
+
+    // ========== 关联字段 ==========
+
+    /**
+     * 委外维修人姓名
+     */
+    private String repairUserName;
+
+    /**
+     * 委外申请人姓名
+     */
+    private String entrustUserName;
+
+    /**
+     * 设备名称
+     */
+    private String sbName;
+
+    /**
+     * 设备编号
+     */
+    private String sbNo;
+
+    /**
+     * 工单号
+     */
+    private String repairNo;
+
+    /**
+     * 完成图片列表
+     */
+    private List<SysFile> finishFileList;
+
+    /**
+     * 完成附件列表
+     */
+    private List<SysFile> finishAttachmentList;
+}

+ 75 - 0
platform-dao/src/main/resources/mapper/repair/RepairEntrustMapper.xml

@@ -0,0 +1,75 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.platform.dao.mapper.repair.RepairEntrustMapper">
+
+    <sql id="Base_Column_List">
+        e.*,
+        user1.real_name as entrust_user_name,
+        user2.real_name as repair_user_name,
+        sb.name as sb_name,
+        sb.no as sb_no,
+        raf.no as repair_no
+    </sql>
+
+    <sql id="Base_From">
+        from t_repair_entrust e
+        left join t_sys_user user1 on e.entrust_user_id = user1.user_id
+        left join t_sys_user user2 on e.repair_user_id = user2.user_id
+        left join t_repair_application_form raf on e.repair_id = raf.id
+        left join t_sb_info sb on raf.sb_id = sb.id
+    </sql>
+
+    <!-- 分页查询委外单列表 -->
+    <select id="selectPageList" parameterType="com.platform.dao.dto.repair.RepairEntrustDTO"
+            resultType="com.platform.dao.vo.repair.RepairEntrustVO">
+        select
+        <include refid="Base_Column_List"/>
+        <include refid="Base_From"/>
+        <where>
+            <!-- 权限过滤:仅申请人和指定维修人可见 -->
+            <if test="currentUserId != null and currentUserId != ''">
+                and (e.entrust_user_id = #{currentUserId} or e.repair_user_id = #{currentUserId})
+            </if>
+            <if test="keyword != null and keyword != ''">
+                and (
+                    e.entrust_no like concat('%',#{keyword},'%') or
+                    raf.no like concat('%',#{keyword},'%') or
+                    sb.name like concat('%',#{keyword},'%') or
+                    sb.no like concat('%',#{keyword},'%') or
+                    user1.real_name like concat('%',#{keyword},'%') or
+                    user2.real_name like concat('%',#{keyword},'%')
+                )
+            </if>
+            <if test="status != null">
+                and e.status = #{status}
+            </if>
+            <if test="repairId != null and repairId != ''">
+                and e.repair_id = #{repairId}
+            </if>
+            <if test="repairUserId != null and repairUserId != ''">
+                and e.repair_user_id = #{repairUserId}
+            </if>
+        </where>
+        order by e.created_time desc
+    </select>
+
+    <!-- 根据工单ID查询委外单列表 -->
+    <select id="selectByRepairId" parameterType="java.lang.String"
+            resultType="com.platform.dao.vo.repair.RepairEntrustVO">
+        select
+        <include refid="Base_Column_List"/>
+        <include refid="Base_From"/>
+        where e.repair_id = #{repairId}
+        order by e.created_time desc
+    </select>
+
+    <!-- 查询委外单详情 -->
+    <select id="selectDetailById" parameterType="java.lang.String"
+            resultType="com.platform.dao.vo.repair.RepairEntrustVO">
+        select
+        <include refid="Base_Column_List"/>
+        <include refid="Base_From"/>
+        where e.id = #{id,jdbcType=VARCHAR}
+    </select>
+
+</mapper>

+ 77 - 0
platform-rest/src/main/java/com/platform/rest/controller/repair/RepairEntrustController.java

@@ -0,0 +1,77 @@
+package com.platform.rest.controller.repair;
+
+import com.platform.common.util.R;
+import com.platform.common.validation.group.AddGroup;
+import com.platform.dao.dto.repair.RepairEntrustDTO;
+import com.platform.dao.vo.repair.RepairEntrustVO;
+import com.platform.rest.log.annotation.SysLog;
+import com.platform.service.repair.RepairEntrustService;
+import lombok.AllArgsConstructor;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @Description 委外单 控制器
+ */
+@RestController
+@AllArgsConstructor
+@RequestMapping("/repair/entrusts")
+public class RepairEntrustController {
+
+    private final RepairEntrustService repairEntrustService;
+
+    /**
+     * 创建委外单
+     */
+    @SysLog("创建委外单")
+    @PostMapping
+    @PreAuthorize("@pms.hasPermission('repair-application-forms-out')")
+    public R create(@Validated({AddGroup.class}) @RequestBody RepairEntrustDTO dto) {
+        repairEntrustService.createEntrust(dto);
+        return new R<>();
+    }
+
+    /**
+     * 完成委外单
+     */
+    @SysLog("完成委外单")
+    @PutMapping("/finish/{id}")
+    @PreAuthorize("@pms.hasPermission('repair-application-forms-out')")
+    public R finish(@PathVariable("id") String id, @RequestBody RepairEntrustDTO dto) {
+        dto.setId(id);
+        repairEntrustService.finishEntrust(dto);
+        return new R<>();
+    }
+
+    /**
+     * 获取委外单详情
+     */
+    @GetMapping("/{id}")
+    @PreAuthorize("@pms.hasPermission('repair-application-forms-out')")
+    public R<RepairEntrustVO> getDetail(@PathVariable("id") String id) {
+        return new R<>(repairEntrustService.getDetailById(id));
+    }
+
+    /**
+     * 委外单分页列表
+     */
+    @GetMapping("/page")
+    @PreAuthorize("@pms.hasPermission('repair-application-forms-out')")
+    public R query(RepairEntrustDTO dto,
+                   @RequestParam(defaultValue = "1") int pageNum,
+                   @RequestParam(defaultValue = "20") int pageSize) {
+        return new R<>(repairEntrustService.selectPageList(dto, pageNum, pageSize));
+    }
+
+    /**
+     * 根据工单ID查询委外单列表
+     */
+    @GetMapping("/by-repair/{repairId}")
+    @PreAuthorize("@pms.hasPermission('repair-application-forms-out')")
+    public R<List<RepairEntrustVO>> getByRepairId(@PathVariable("repairId") String repairId) {
+        return new R<>(repairEntrustService.getByRepairId(repairId));
+    }
+}

+ 46 - 0
platform-service/src/main/java/com/platform/service/repair/RepairEntrustService.java

@@ -0,0 +1,46 @@
+package com.platform.service.repair;
+
+import com.platform.dao.bean.MyVOPage;
+import com.platform.dao.dto.repair.RepairEntrustDTO;
+import com.platform.dao.vo.repair.RepairEntrustVO;
+
+import java.util.List;
+
+/**
+ * @Description 委外单 Service 接口
+ */
+public interface RepairEntrustService {
+
+    /**
+     * 创建委外单
+     * 1. 创建委外单记录
+     * 2. 更新原工单状态为 ENTRUST_REPAIR(9)
+     * 3. 生成委外单号
+     */
+    void createEntrust(RepairEntrustDTO dto);
+
+    /**
+     * 完成委外单
+     * 1. 校验委外单状态必须为 PROCESSING
+     * 2. 校验 finishRemark 不为空
+     * 3. 保存完成图片/附件
+     * 4. 更新委外单状态为 FINISHED
+     * 5. 更新原工单状态回到 PROCESSING(2)
+     */
+    void finishEntrust(RepairEntrustDTO dto);
+
+    /**
+     * 根据工单ID查询委外单列表
+     */
+    List<RepairEntrustVO> getByRepairId(String repairId);
+
+    /**
+     * 获取委外单详情(含文件)
+     */
+    RepairEntrustVO getDetailById(String id);
+
+    /**
+     * 分页查询委外单列表(权限过滤:仅申请人和指定维修人可见)
+     */
+    MyVOPage<RepairEntrustVO> selectPageList(RepairEntrustDTO dto, int pageNum, int pageSize);
+}

+ 3 - 0
platform-service/src/main/java/com/platform/service/repair/impl/RepairApplicationFormServiceImpl.java

@@ -856,6 +856,9 @@ public class RepairApplicationFormServiceImpl
     @Override
     public void examine(String id) {
         RepairApplicationForm applicationForm = mapper.selectById(id);
+        if (RepairApplicationFormStatusEnum.ENTRUST_REPAIR.getValue().equals(applicationForm.getStatus())) {
+            throw new BusinessException("委外维修中,不允许提交审核");
+        }
         if (!RepairApplicationFormStatusEnum.WAIT_SUBMIT.getValue().equals(applicationForm.getStatus())) {
             throw new BusinessException("未完成的不允许提交审核");
         }

+ 222 - 0
platform-service/src/main/java/com/platform/service/repair/impl/RepairEntrustServiceImpl.java

@@ -0,0 +1,222 @@
+package com.platform.service.repair.impl;
+
+import cn.hutool.core.collection.CollectionUtil;
+import com.github.pagehelper.PageHelper;
+import com.platform.common.exception.BusinessException;
+import com.platform.common.model.UserInfo;
+import com.platform.common.util.IdGeneratorUtils;
+import com.platform.common.util.SecurityUtils;
+import com.platform.common.util.StringUtils;
+import com.platform.dao.bean.MyVOPage;
+import com.platform.dao.dto.repair.RepairEntrustDTO;
+import com.platform.dao.entity.repair.RepairApplicationForm;
+import com.platform.dao.entity.repair.RepairEntrust;
+import com.platform.dao.entity.upms.SysFile;
+import com.platform.dao.enums.RepairApplicationFormStatusEnum;
+import com.platform.dao.enums.RepairEntrustStatusEnum;
+import com.platform.dao.enums.SysFileTypeEnum;
+import com.platform.dao.mapper.repair.RepairApplicationFormMapper;
+import com.platform.dao.mapper.repair.RepairEntrustMapper;
+import com.platform.dao.mapper.upms.SysFileMapper;
+import com.platform.dao.vo.repair.RepairEntrustVO;
+import com.platform.service.base.impl.BaseServiceImpl;
+import com.platform.service.repair.RepairEntrustService;
+import com.platform.service.util.SysFileUtils;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import tk.mybatis.mapper.entity.Example;
+import tk.mybatis.mapper.weekend.Weekend;
+import tk.mybatis.mapper.weekend.WeekendCriteria;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+/**
+ * @Description 委外单 Service 实现类
+ */
+@AllArgsConstructor
+@Service("repairEntrustService")
+@Slf4j
+public class RepairEntrustServiceImpl
+        extends BaseServiceImpl<RepairEntrustMapper, RepairEntrust, RepairEntrustDTO>
+        implements RepairEntrustService {
+
+    private final RepairApplicationFormMapper repairApplicationFormMapper;
+    private final SysFileMapper sysFileMapper;
+
+    /**
+     * 创建委外单
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void createEntrust(RepairEntrustDTO dto) {
+        // 1. 校验工单是否存在且状态为维修中
+        RepairApplicationForm applicationForm = repairApplicationFormMapper.selectById(dto.getRepairId());
+        if (applicationForm == null) {
+            throw new BusinessException("工单不存在");
+        }
+        if (!RepairApplicationFormStatusEnum.PROCESSING.getValue().equals(applicationForm.getStatus())) {
+            throw new BusinessException("工单当前状态不允许委外,仅维修中的工单可以委外");
+        }
+
+        // 2. 生成委外单号
+        int count = getCountByDTO(new RepairEntrustDTO());
+        String entrustNo = IdGeneratorUtils.getEntrustNo(++count);
+        if (StringUtils.isNotBlank(dto.getEntrustNo())) {
+            entrustNo = dto.getEntrustNo();
+        }
+
+        // 3. 获取当前登录用户作为委外申请人
+        UserInfo userInfo = SecurityUtils.getUserInfo();
+
+        // 4. 创建委外单记录
+        RepairEntrust entrust = new RepairEntrust();
+        entrust.setId(IdGeneratorUtils.getObjectId());
+        entrust.setRepairId(dto.getRepairId());
+        entrust.setEntrustNo(entrustNo);
+        entrust.setOutType(dto.getOutType());
+        entrust.setEntrustUserId(userInfo.getUserId());
+        entrust.setRepairUserId(dto.getRepairUserId());
+        entrust.setReason(dto.getReason());
+        entrust.setStatus(RepairEntrustStatusEnum.PROCESSING.getValue());
+        entrust.setCreatedTime(LocalDateTime.now());
+        entrust.setUpdateTime(LocalDateTime.now());
+        entrust.setCreatedUserId(userInfo.getUserId());
+        entrust.setUpdateUserId(userInfo.getUserId());
+        mapper.insertSelective(entrust);
+
+        // 5. 更新原工单状态为委外维修中
+        applicationForm.setStatus(RepairApplicationFormStatusEnum.ENTRUST_REPAIR.getValue());
+        applicationForm.setUpdateTime(LocalDateTime.now());
+        repairApplicationFormMapper.updateByPrimaryKeySelective(applicationForm);
+    }
+
+    /**
+     * 完成委外单
+     */
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void finishEntrust(RepairEntrustDTO dto) {
+        // 1. 查询委外单
+        RepairEntrust entrust = mapper.selectByPrimaryKey(dto.getId());
+        if (entrust == null) {
+            throw new BusinessException("委外单不存在");
+        }
+
+        // 2. 校验状态
+        if (!RepairEntrustStatusEnum.PROCESSING.getValue().equals(entrust.getStatus())) {
+            throw new BusinessException("委外单当前状态不允许完成");
+        }
+
+        // 3. 校验维修情况必填
+        if (StringUtils.isBlank(dto.getFinishRemark())) {
+            throw new BusinessException("维修情况不能为空");
+        }
+
+        // 4. 保存完成图片/附件
+        saveFinishFile(dto);
+
+        // 5. 更新委外单状态为已完成
+        entrust.setStatus(RepairEntrustStatusEnum.FINISHED.getValue());
+        entrust.setFinishRemark(dto.getFinishRemark());
+        entrust.setFinishTime(LocalDateTime.now());
+        entrust.setUpdateTime(LocalDateTime.now());
+        UserInfo userInfo = SecurityUtils.getUserInfo();
+        entrust.setUpdateUserId(userInfo.getUserId());
+        mapper.updateByPrimaryKeySelective(entrust);
+
+        // 6. 更新原工单状态回到维修中
+        RepairApplicationForm applicationForm = repairApplicationFormMapper.selectById(entrust.getRepairId());
+        if (applicationForm != null) {
+            applicationForm.setStatus(RepairApplicationFormStatusEnum.PROCESSING.getValue());
+            applicationForm.setUpdateTime(LocalDateTime.now());
+            repairApplicationFormMapper.updateByPrimaryKeySelective(applicationForm);
+        }
+    }
+
+    /**
+     * 根据工单ID查询委外单列表
+     */
+    @Override
+    public List<RepairEntrustVO> getByRepairId(String repairId) {
+        return mapper.selectByRepairId(repairId);
+    }
+
+    /**
+     * 获取委外单详情(含文件)
+     */
+    @Override
+    public RepairEntrustVO getDetailById(String id) {
+        RepairEntrustVO vo = mapper.selectDetailById(id);
+        if (vo != null) {
+            // 加载完成图片
+            vo.setFinishFileList(getFileListByType(id, SysFileTypeEnum.ENTRUST_FINISH_IMGS.getValue()));
+            // 加载完成附件
+            vo.setFinishAttachmentList(getFileListByType(id, SysFileTypeEnum.ENTRUST_FINISH_FILES.getValue()));
+        }
+        return vo;
+    }
+
+    /**
+     * 分页查询委外单列表(权限过滤)
+     */
+    @Override
+    public MyVOPage<RepairEntrustVO> selectPageList(RepairEntrustDTO dto, int pageNum, int pageSize) {
+        // 清除可能残留的分页参数,避免线程复用导致分页污染其他查询
+        PageHelper.clearPage();
+        PageHelper.startPage(pageNum, pageSize);
+        try {
+            // 设置当前用户ID用于权限过滤
+            UserInfo userInfo = SecurityUtils.getUserInfo();
+            dto.setCurrentUserId(userInfo.getUserId());
+            return new MyVOPage<>(mapper.selectPageList(dto));
+        } finally {
+            PageHelper.clearPage();
+        }
+    }
+
+    /**
+     * 保存完成时的图片/附件
+     */
+    private void saveFinishFile(RepairEntrustDTO dto) {
+        // 保存完成图片
+        if (CollectionUtil.isNotEmpty(dto.getFinishFileList())) {
+            Weekend<SysFile> weekend = new Weekend<>(SysFile.class);
+            weekend.weekendCriteria()
+                    .andEqualTo(SysFile::getType, SysFileTypeEnum.ENTRUST_FINISH_IMGS.getValue())
+                    .andEqualTo(SysFile::getTargetId, dto.getId());
+            sysFileMapper.deleteByExample(weekend);
+            List<SysFile> list = SysFileUtils.changeFileList(
+                    dto.getFinishFileList(), dto.getId(), SysFileTypeEnum.ENTRUST_FINISH_IMGS.getValue());
+            if (CollectionUtil.isNotEmpty(list)) {
+                sysFileMapper.insertListforComplex(list);
+            }
+        }
+        // 保存完成附件
+        if (CollectionUtil.isNotEmpty(dto.getFinishAttachmentList())) {
+            Weekend<SysFile> weekend = new Weekend<>(SysFile.class);
+            weekend.weekendCriteria()
+                    .andEqualTo(SysFile::getType, SysFileTypeEnum.ENTRUST_FINISH_FILES.getValue())
+                    .andEqualTo(SysFile::getTargetId, dto.getId());
+            sysFileMapper.deleteByExample(weekend);
+            List<SysFile> list = SysFileUtils.changeFileList(
+                    dto.getFinishAttachmentList(), dto.getId(), SysFileTypeEnum.ENTRUST_FINISH_FILES.getValue());
+            if (CollectionUtil.isNotEmpty(list)) {
+                sysFileMapper.insertListforComplex(list);
+            }
+        }
+    }
+
+    /**
+     * 根据类型获取文件列表
+     */
+    private List<SysFile> getFileListByType(String targetId, Integer type) {
+        Weekend<SysFile> weekend = new Weekend<>(SysFile.class);
+        WeekendCriteria<SysFile, Object> weekendCriteria = weekend.weekendCriteria();
+        weekendCriteria.andEqualTo(SysFile::getTargetId, targetId)
+                .andEqualTo(SysFile::getType, type);
+        return sysFileMapper.selectByExample(weekend);
+    }
+}