From 5bce6fa4728a9fb62b95e94828fddf8f3134a409 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 14 八月 2026 15:55:31 +0800
Subject: [PATCH] feat(bpm): 添加业务附件配置功能

---
 approval-detail-before.png                                                                                                   |    0 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachRespVO.java    |   33 +++
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachSaveReqVO.java |   36 ++++
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/attach/BpmBusinessAttachMapper.java                     |   32 +++
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachServiceImpl.java                  |  119 +++++++++++++
 approval-detail-v2.jpeg                                                                                                      |    0 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/BpmBusinessAttachController.java          |   72 ++++++++
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java                   |   13 +
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachPageReqVO.java |   37 ++++
 approval-detail-before-full.png                                                                                              |    0 
 contract-approve-detail.png                                                                                                  |    0 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java                                     |    4 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java           |   13 +
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmBusinessAttachDO.java                |   55 ++++++
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachService.java                      |   75 ++++++++
 15 files changed, 485 insertions(+), 4 deletions(-)

diff --git a/approval-detail-before-full.png b/approval-detail-before-full.png
new file mode 100644
index 0000000..8173a54
--- /dev/null
+++ b/approval-detail-before-full.png
Binary files differ
diff --git a/approval-detail-before.png b/approval-detail-before.png
new file mode 100644
index 0000000..d7d7e55
--- /dev/null
+++ b/approval-detail-before.png
Binary files differ
diff --git a/approval-detail-v2.jpeg b/approval-detail-v2.jpeg
new file mode 100644
index 0000000..6c26c21
--- /dev/null
+++ b/approval-detail-v2.jpeg
Binary files differ
diff --git a/contract-approve-detail.png b/contract-approve-detail.png
new file mode 100644
index 0000000..723c4c5
--- /dev/null
+++ b/contract-approve-detail.png
Binary files differ
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/BpmBusinessAttachController.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/BpmBusinessAttachController.java
new file mode 100644
index 0000000..5e783ea
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/BpmBusinessAttachController.java
@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.attach;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachRespVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachSaveReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmBusinessAttachDO;
+import cn.iocoder.yudao.module.bpm.service.attach.BpmBusinessAttachService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - BPM 涓氬姟闄勪欢閰嶇疆")
+@RestController
+@RequestMapping("/bpm/business-attach")
+@Validated
+public class BpmBusinessAttachController {
+
+    @Resource
+    private BpmBusinessAttachService businessAttachService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓涓氬姟闄勪欢閰嶇疆")
+    @PreAuthorize("@ss.hasPermission('bpm:business-attach:create')")
+    public CommonResult<Long> createBusinessAttach(@Valid @RequestBody BpmBusinessAttachSaveReqVO createReqVO) {
+        return success(businessAttachService.createBusinessAttach(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊涓氬姟闄勪欢閰嶇疆")
+    @PreAuthorize("@ss.hasPermission('bpm:business-attach:update')")
+    public CommonResult<Boolean> updateBusinessAttach(@Valid @RequestBody BpmBusinessAttachSaveReqVO updateReqVO) {
+        businessAttachService.updateBusinessAttach(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎涓氬姟闄勪欢閰嶇疆")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:business-attach:delete')")
+    public CommonResult<Boolean> deleteBusinessAttach(@RequestParam("id") Long id) {
+        businessAttachService.deleteBusinessAttach(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱涓氬姟闄勪欢閰嶇疆")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('bpm:business-attach:query')")
+    public CommonResult<BpmBusinessAttachRespVO> getBusinessAttach(@RequestParam("id") Long id) {
+        BpmBusinessAttachDO attach = businessAttachService.getBusinessAttach(id);
+        return success(BeanUtils.toBean(attach, BpmBusinessAttachRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱涓氬姟闄勪欢閰嶇疆鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('bpm:business-attach:query')")
+    public CommonResult<PageResult<BpmBusinessAttachRespVO>> getBusinessAttachPage(@Valid BpmBusinessAttachPageReqVO pageReqVO) {
+        PageResult<BpmBusinessAttachDO> pageResult = businessAttachService.getBusinessAttachPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, BpmBusinessAttachRespVO.class));
+    }
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachPageReqVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachPageReqVO.java
new file mode 100644
index 0000000..2790b99
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachPageReqVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "绠$悊鍚庡彴 - BPM 涓氬姟闄勪欢閰嶇疆鍒嗛〉 Request VO")
+@Data
+public class BpmBusinessAttachPageReqVO extends PageParam {
+
+    @Schema(description = "娴佺▼瀹氫箟鐨� Key", example = "purchase_approve")
+    private String processDefinitionKey;
+
+    @Schema(description = "涓氬姟绫诲瀷锛屼緥濡� erp_purchase_order", example = "erp_purchase_order")
+    private String businessType;
+
+    @Schema(description = "涓氬姟璇︽儏缁勪欢璺緞锛屼緥濡� /erp/purchase/order/detail/index.vue")
+    private String componentPath;
+
+    @Schema(description = "鐘舵�侊紝鍙傝 CommonStatusEnum 鏋氫妇", example = "1")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachRespVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachRespVO.java
new file mode 100644
index 0000000..7778dd1
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachRespVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - BPM 涓氬姟闄勪欢閰嶇疆 Response VO")
+@Data
+public class BpmBusinessAttachRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "娴佺▼瀹氫箟鐨� Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "purchase_approve")
+    private String processDefinitionKey;
+
+    @Schema(description = "涓氬姟绫诲瀷锛屼緥濡� erp_purchase_order", example = "erp_purchase_order")
+    private String businessType;
+
+    @Schema(description = "涓氬姟璇︽儏缁勪欢璺緞锛屼緥濡� /erp/purchase/order/detail/index.vue", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String componentPath;
+
+    @Schema(description = "鐘舵�侊紝鍙傝 CommonStatusEnum 鏋氫妇", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "閲囪喘璁㈠崟瀹℃壒灞曠ず閲囪喘璁㈠崟璇︽儏")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachSaveReqVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachSaveReqVO.java
new file mode 100644
index 0000000..0e659a1
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachSaveReqVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - BPM 涓氬姟闄勪欢閰嶇疆鏂板/淇敼 Request VO")
+@Data
+public class BpmBusinessAttachSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "娴佺▼瀹氫箟鐨� Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "purchase_approve")
+    @NotEmpty(message = "娴佺▼瀹氫箟鐨� Key 涓嶈兘涓虹┖")
+    private String processDefinitionKey;
+
+    @Schema(description = "涓氬姟绫诲瀷锛屼緥濡� erp_purchase_order", example = "erp_purchase_order")
+    private String businessType;
+
+    @Schema(description = "涓氬姟璇︽儏缁勪欢璺緞锛屼緥濡� /erp/purchase/order/detail/index.vue", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "涓氬姟璇︽儏缁勪欢璺緞涓嶈兘涓虹┖")
+    private String componentPath;
+
+    @Schema(description = "鐘舵�侊紝鍙傝 CommonStatusEnum 鏋氫妇", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鐘舵�佷笉鑳戒负绌�")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "閲囪喘璁㈠崟瀹℃壒灞曠ず閲囪喘璁㈠崟璇︽儏")
+    private String remark;
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
index add6f20..7d5705b 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
@@ -11,6 +11,7 @@
 import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.service.attach.BpmBusinessAttachService;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
@@ -56,6 +57,8 @@
     private BpmProcessDefinitionService processDefinitionService;
     @Resource
     private BpmCategoryService categoryService;
+    @Resource
+    private BpmBusinessAttachService businessAttachService;
 
     @Resource
     private AdminUserApi adminUserApi;
@@ -217,10 +220,16 @@
         List<HistoricTaskInstance> tasks = taskService.getFinishedTaskListByProcessInstanceIdWithoutCancel(processInstanceId);
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                 convertSet(tasks, item -> Long.valueOf(item.getAssignee())));
-        return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePrintData(historicProcessInstance,
+        BpmProcessPrintDataRespVO printData = BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePrintData(
+                historicProcessInstance,
                 processDefinitionService.getProcessDefinitionInfo(historicProcessInstance.getProcessDefinitionId()),
                 tasks, userMap,
-                new UserSimpleBaseVO().setNickname(startUser.getNickname()).setDeptName(dept.getName())));
+                new UserSimpleBaseVO().setNickname(startUser.getNickname()).setDeptName(dept.getName()));
+        // 濉厖涓氬姟闄勪欢閰嶇疆锛屽疄鐜版墦鍗拌嚜鍔ㄥ睍绀哄閮ㄤ笟鍔′俊鎭�
+        businessAttachService.fillBusinessForm(printData.getProcessInstance().getProcessDefinition(),
+                processDefinitionService.getProcessDefinition(historicProcessInstance.getProcessDefinitionId()).getKey(),
+                historicProcessInstance.getBusinessKey());
+        return success(printData);
     }
 
 }
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmBusinessAttachDO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmBusinessAttachDO.java
new file mode 100644
index 0000000..b35ec32
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmBusinessAttachDO.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * BPM 涓氬姟闄勪欢閰嶇疆 DO
+ *
+ * 鐢ㄤ簬寤虹珛銆屾祦绋嬪畾涔� Key -> 涓氬姟璇︽儏缁勪欢銆嶇殑鏄犲皠锛屽疄鐜板鎵硅鎯呰嚜鍔ㄥ睍绀哄閮ㄤ笟鍔′俊鎭�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("bpm_business_attach")
+@KeySequence("bpm_business_attach_seq") // 鐢ㄤ簬 Oracle銆丳ostgreSQL銆並ingbase銆丏B2銆丠2 鏁版嵁搴撶殑涓婚敭鑷銆傚鏋滄槸 MySQL 绛夋暟鎹簱锛屽彲涓嶅啓銆�
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmBusinessAttachDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 娴佺▼瀹氫箟鐨� Key
+     */
+    private String processDefinitionKey;
+    /**
+     * 涓氬姟绫诲瀷锛屼緥濡� erp_purchase_order
+     */
+    private String businessType;
+    /**
+     * 涓氬姟璇︽儏缁勪欢璺緞锛屼緥濡� /erp/purchase/order/detail/index.vue
+     */
+    private String componentPath;
+    /**
+     * 鐘舵��
+     *
+     * 鏋氫妇 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/attach/BpmBusinessAttachMapper.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/attach/BpmBusinessAttachMapper.java
new file mode 100644
index 0000000..05cc9fe
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/attach/BpmBusinessAttachMapper.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.bpm.dal.mysql.attach;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachPageReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmBusinessAttachDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * BPM 涓氬姟闄勪欢閰嶇疆 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface BpmBusinessAttachMapper extends BaseMapperX<BpmBusinessAttachDO> {
+
+    default PageResult<BpmBusinessAttachDO> selectPage(BpmBusinessAttachPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BpmBusinessAttachDO>()
+                .likeIfPresent(BpmBusinessAttachDO::getProcessDefinitionKey, reqVO.getProcessDefinitionKey())
+                .likeIfPresent(BpmBusinessAttachDO::getBusinessType, reqVO.getBusinessType())
+                .likeIfPresent(BpmBusinessAttachDO::getComponentPath, reqVO.getComponentPath())
+                .eqIfPresent(BpmBusinessAttachDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(BpmBusinessAttachDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(BpmBusinessAttachDO::getId));
+    }
+
+    default BpmBusinessAttachDO selectByProcessDefinitionKey(String processDefinitionKey) {
+        return selectOne(BpmBusinessAttachDO::getProcessDefinitionKey, processDefinitionKey);
+    }
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
index 0537fe3..a598ff5 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
@@ -89,4 +89,8 @@
     // ========== BPM 娴佺▼琛ㄨ揪寮� 1-009-014-000 ==========
     ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "娴佺▼琛ㄨ揪寮忎笉瀛樺湪");
 
+    // ========== BPM 涓氬姟闄勪欢閰嶇疆 1-009-015-000 ==========
+    ErrorCode BUSINESS_ATTACH_NOT_EXISTS = new ErrorCode(1_009_015_000, "涓氬姟闄勪欢閰嶇疆涓嶅瓨鍦�");
+    ErrorCode BUSINESS_ATTACH_PROCESS_DEFINITION_KEY_DUPLICATE = new ErrorCode(1_009_015_001, "娴佺▼瀹氫箟鐨� Key({})宸插瓨鍦�");
+
 }
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachService.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachService.java
new file mode 100644
index 0000000..734d37f
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachService.java
@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.bpm.service.attach;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachSaveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmBusinessAttachDO;
+import jakarta.validation.Valid;
+
+/**
+ * BPM 涓氬姟闄勪欢閰嶇疆 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface BpmBusinessAttachService {
+
+    /**
+     * 鍒涘缓涓氬姟闄勪欢閰嶇疆
+     *
+     * @param createReqVO 鍒涘缓淇℃伅
+     * @return 缂栧彿
+     */
+    Long createBusinessAttach(@Valid BpmBusinessAttachSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊涓氬姟闄勪欢閰嶇疆
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updateBusinessAttach(@Valid BpmBusinessAttachSaveReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎涓氬姟闄勪欢閰嶇疆
+     *
+     * @param id 缂栧彿
+     */
+    void deleteBusinessAttach(Long id);
+
+    /**
+     * 鑾峰緱涓氬姟闄勪欢閰嶇疆
+     *
+     * @param id 缂栧彿
+     * @return 涓氬姟闄勪欢閰嶇疆
+     */
+    BpmBusinessAttachDO getBusinessAttach(Long id);
+
+    /**
+     * 鑾峰緱涓氬姟闄勪欢閰嶇疆鍒嗛〉
+     *
+     * @param pageReqVO 鍒嗛〉鏌ヨ
+     * @return 涓氬姟闄勪欢閰嶇疆鍒嗛〉
+     */
+    PageResult<BpmBusinessAttachDO> getBusinessAttachPage(BpmBusinessAttachPageReqVO pageReqVO);
+
+    /**
+     * 鑾峰緱涓氬姟闄勪欢閰嶇疆锛屽熀浜庢祦绋嬪畾涔� Key
+     *
+     * @param processDefinitionKey 娴佺▼瀹氫箟 Key
+     * @return 涓氬姟闄勪欢閰嶇疆
+     */
+    BpmBusinessAttachDO getBusinessAttachByProcessDefinitionKey(String processDefinitionKey);
+
+    /**
+     * 濉厖涓氬姟琛ㄥ崟淇℃伅鍒版祦绋嬪畾涔� Response
+     *
+     * 鑻ヨ娴佺▼瀹氫箟閰嶇疆浜嗕笟鍔¢檮浠讹紙涓斿凡鍚敤銆佸綋鍓嶆槸 NORMAL 娴佺▼琛ㄥ崟銆佸瓨鍦ㄤ笟鍔′富閿級锛�
+     * 鍒欏皢 formType 瑕嗙洊涓� CUSTOM 骞舵敞鍏ヤ笟鍔¤鎯呯粍浠惰矾寰勶紝瀹炵幇瀹℃壒璇︽儏鑷姩灞曠ず涓氬姟淇℃伅
+     *
+     * @param definitionResp        娴佺▼瀹氫箟 Response
+     * @param processDefinitionKey  娴佺▼瀹氫箟 Key
+     * @param businessKey           涓氬姟鐨勫敮涓�鏍囪瘑锛堟湭鍙戣捣鏃朵负 null锛�
+     */
+    void fillBusinessForm(BpmProcessDefinitionRespVO definitionResp, String processDefinitionKey, String businessKey);
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachServiceImpl.java
new file mode 100644
index 0000000..2d14ef5
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachServiceImpl.java
@@ -0,0 +1,119 @@
+package cn.iocoder.yudao.module.bpm.service.attach;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachSaveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmBusinessAttachDO;
+import cn.iocoder.yudao.module.bpm.dal.mysql.attach.BpmBusinessAttachMapper;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.BUSINESS_ATTACH_NOT_EXISTS;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.BUSINESS_ATTACH_PROCESS_DEFINITION_KEY_DUPLICATE;
+
+/**
+ * BPM 涓氬姟闄勪欢閰嶇疆 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class BpmBusinessAttachServiceImpl implements BpmBusinessAttachService {
+
+    @Resource
+    private BpmBusinessAttachMapper businessAttachMapper;
+
+    @Override
+    public Long createBusinessAttach(BpmBusinessAttachSaveReqVO createReqVO) {
+        // 鏍¢獙娴佺▼瀹氫箟 Key 鍞竴
+        validateProcessDefinitionKeyUnique(createReqVO);
+        // 鎻掑叆
+        BpmBusinessAttachDO attach = BeanUtils.toBean(createReqVO, BpmBusinessAttachDO.class);
+        businessAttachMapper.insert(attach);
+        return attach.getId();
+    }
+
+    @Override
+    public void updateBusinessAttach(BpmBusinessAttachSaveReqVO updateReqVO) {
+        // 鏍¢獙瀛樺湪
+        validateBusinessAttachExists(updateReqVO.getId());
+        // 鏍¢獙娴佺▼瀹氫箟 Key 鍞竴
+        validateProcessDefinitionKeyUnique(updateReqVO);
+        // 鏇存柊
+        BpmBusinessAttachDO updateObj = BeanUtils.toBean(updateReqVO, BpmBusinessAttachDO.class);
+        businessAttachMapper.updateById(updateObj);
+    }
+
+    private void validateProcessDefinitionKeyUnique(BpmBusinessAttachSaveReqVO reqVO) {
+        BpmBusinessAttachDO attach = businessAttachMapper.selectByProcessDefinitionKey(reqVO.getProcessDefinitionKey());
+        if (attach == null || ObjUtil.equal(attach.getId(), reqVO.getId())) {
+            return;
+        }
+        throw exception(BUSINESS_ATTACH_PROCESS_DEFINITION_KEY_DUPLICATE, reqVO.getProcessDefinitionKey());
+    }
+
+    @Override
+    public void deleteBusinessAttach(Long id) {
+        // 鏍¢獙瀛樺湪
+        validateBusinessAttachExists(id);
+        // 鍒犻櫎
+        businessAttachMapper.deleteById(id);
+    }
+
+    private BpmBusinessAttachDO validateBusinessAttachExists(Long id) {
+        BpmBusinessAttachDO attach = businessAttachMapper.selectById(id);
+        if (attach == null) {
+            throw exception(BUSINESS_ATTACH_NOT_EXISTS);
+        }
+        return attach;
+    }
+
+    @Override
+    public BpmBusinessAttachDO getBusinessAttach(Long id) {
+        return businessAttachMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<BpmBusinessAttachDO> getBusinessAttachPage(BpmBusinessAttachPageReqVO pageReqVO) {
+        return businessAttachMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public BpmBusinessAttachDO getBusinessAttachByProcessDefinitionKey(String processDefinitionKey) {
+        return businessAttachMapper.selectByProcessDefinitionKey(processDefinitionKey);
+    }
+
+    @Override
+    public void fillBusinessForm(BpmProcessDefinitionRespVO definitionResp, String processDefinitionKey, String businessKey) {
+        // 鏈彂璧� / 鏃犱笟鍔′富閿細涓嶆敞鍏�
+        if (definitionResp == null || StrUtil.isBlank(businessKey)) {
+            return;
+        }
+        // 闈� NORMAL 娴佺▼琛ㄥ崟锛堝凡鎵嬪姩閰嶇疆 CUSTOM锛夛細涓嶈鐩�
+        if (!Objects.equals(definitionResp.getFormType(), BpmModelFormTypeEnum.NORMAL.getType())) {
+            return;
+        }
+        // 鏈懡涓厤缃� / 閰嶇疆琚鐢細淇濇寔鍘熸牱
+        BpmBusinessAttachDO attach = getBusinessAttachByProcessDefinitionKey(processDefinitionKey);
+        if (attach == null || !Objects.equals(attach.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
+            return;
+        }
+        // 瑕嗙洊涓� CUSTOM锛屾敞鍏ヤ笟鍔¤鎯呯粍浠惰矾寰�
+        definitionResp.setFormType(BpmModelFormTypeEnum.CUSTOM.getType());
+        definitionResp.setFormCustomViewPath(attach.getComponentPath());
+        // 娓呯┖鏅�氳〃鍗曞瓧娈碉細璇︽儏椤� CUSTOM 鍒嗘敮涓嶈锛涙墦鍗伴〉鎸� formFields.length == 0 鍒ゆ柇娓叉煋涓氬姟缁勪欢
+        definitionResp.setFormFields(null);
+        definitionResp.setFormConf(null);
+    }
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
index f7d2dd5..85ff097 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -37,6 +37,7 @@
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
+import cn.iocoder.yudao.module.bpm.service.attach.BpmBusinessAttachService;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -123,6 +124,9 @@
 
     @Resource
     private BpmProcessIdRedisDAO processIdRedisDAO;
+
+    @Resource
+    private BpmBusinessAttachService businessAttachService;
 
     // ========== Query 鏌ヨ鐩稿叧鏂规硶 ==========
 
@@ -389,9 +393,14 @@
         Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), taskId);
 
         // 3. 鎷兼帴鏁版嵁
-        return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition,
-                processDefinitionInfo, processInstance,
+        BpmApprovalDetailRespVO detailRespVO = BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel,
+                processDefinition, processDefinitionInfo, processInstance,
                 processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap);
+
+        // 4. 濉厖涓氬姟闄勪欢閰嶇疆锛屽疄鐜板鎵硅鎯呰嚜鍔ㄥ睍绀哄閮ㄤ笟鍔′俊鎭�
+        businessAttachService.fillBusinessForm(detailRespVO.getProcessDefinition(), processDefinition.getKey(),
+                processInstance != null ? processInstance.getBusinessKey() : null);
+        return detailRespVO;
     }
 
     /**

--
Gitblit v1.9.3