# Conflicts:
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmActivityController.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmTaskController.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmApprovalDetailReqVO.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/instance/BpmFormFieldsPermissionReqVO.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskApproveReqVO.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/vo/task/BpmTaskReturnReqVO.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderMultiStrategy.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserDeptLeaderStrategy.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/dept/BpmTaskCandidateStartUserSelectStrategy.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/other/BpmTaskCandidateAssignEmptyStrategy.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateRoleStrategy.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/candidate/strategy/user/BpmTaskCandidateStartUserStrategy.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/SimpleModelUtils.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmProcessDefinitionServiceImpl.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmActivityServiceImpl.java
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
#	yudao-module-mall/yudao-module-promotion-biz/src/main/java/cn/iocoder/yudao/module/promotion/controller/admin/discount/vo/DiscountActivityBaseVO.java
pull/158/MERGE
YunaiV 2024-11-23 11:13:01 +08:00
commit 14c49629b7
120 changed files with 3176 additions and 1652 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View File

@ -152,18 +152,22 @@
### 工作流程
| | 功能 | 描述 |
|-----|-------|----------------------------------------|
| 🚀 | 流程模型 | 配置工作流的流程模型,支持文件导入与在线设计流程图,提供 7 种任务分配规则 |
| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转发、委派、退回等操作 |
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,未来会支持回退操作 |
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
| | 功能 | 描述 |
|----|-------|-----------------------------------------|
| 🚀 | 流程模型 | 配置工作流的流程模型,支持 BPMN 和仿钉钉/飞书设计器 |
| 🚀 | 流程表单 | 拖动表单元素生成相应的工作流表单,覆盖 Element UI 所有的表单组件 |
| 🚀 | 用户分组 | 自定义用户分组,可用于工作流的审批分组 |
| 🚀 | 我的流程 | 查看我发起的工作流程,支持新建、取消流程等操作,高亮流程图、审批时间线 |
| 🚀 | 待办任务 | 查看自己【未】审批的工作任务,支持通过、不通过、转派、委派、退回、加减签等操作 |
| 🚀 | 已办任务 | 查看自己【已】审批的工作任务,支持流程预测,展示未来审批人信息 |
| 🚀 | OA 请假 | 作为业务自定义接入工作流的使用示例,只需创建请求对应的工作流程,即可进行审批 |
![功能图](/.image/common/bpm-feature.png)
| BPMN 设计器 | 钉钉/飞书设计器 |
|------------------------------|--------------------------------|
| ![](/.image/工作流设计器-bpmn.jpg) | ![](/.image/工作流设计器-simple.jpg) |
### 支付系统
| | 功能 | 描述 |

View File

@ -11,7 +11,7 @@
Target Server Version : 80200 (8.2.0)
File Encoding : 65001
Date: 09/11/2024 18:16:12
Date: 23/11/2024 10:16:46
*/
SET NAMES utf8mb4;
@ -91,7 +91,7 @@ CREATE TABLE `infra_api_error_log` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 21213 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
) ENGINE = InnoDB AUTO_INCREMENT = 21220 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统异常日志';
-- ----------------------------
-- Records of infra_api_error_log
@ -250,7 +250,7 @@ CREATE TABLE `infra_file` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1558 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
) ENGINE = InnoDB AUTO_INCREMENT = 1561 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '文件表';
-- ----------------------------
-- Records of infra_file
@ -1044,7 +1044,7 @@ CREATE TABLE `system_login_log` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3370 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
) ENGINE = InnoDB AUTO_INCREMENT = 3395 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '系统访问记录';
-- ----------------------------
-- Records of system_login_log
@ -2165,7 +2165,7 @@ CREATE TABLE `system_oauth2_access_token` (
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_access_token`(`access_token` ASC) USING BTREE,
INDEX `idx_refresh_token`(`refresh_token` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 11308 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
) ENGINE = InnoDB AUTO_INCREMENT = 11844 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 访问令牌';
-- ----------------------------
-- Records of system_oauth2_access_token
@ -2287,7 +2287,7 @@ CREATE TABLE `system_oauth2_refresh_token` (
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1676 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
) ENGINE = InnoDB AUTO_INCREMENT = 1696 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'OAuth2 刷新令牌';
-- ----------------------------
-- Records of system_oauth2_refresh_token
@ -2308,6 +2308,7 @@ CREATE TABLE `system_operate_log` (
`sub_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '操作名',
`biz_id` bigint NOT NULL COMMENT '操作数据模块编号',
`action` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '操作内容',
`success` bit(1) NOT NULL DEFAULT b'1' COMMENT '操作结果',
`extra` varchar(2000) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '拓展字段',
`request_method` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '请求方法名',
`request_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '请求地址',
@ -3303,7 +3304,7 @@ CREATE TABLE `system_sms_code` (
`tenant_id` bigint NOT NULL DEFAULT 0 COMMENT '租户编号',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_mobile`(`mobile` ASC) USING BTREE COMMENT '手机号'
) ENGINE = InnoDB AUTO_INCREMENT = 642 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
) ENGINE = InnoDB AUTO_INCREMENT = 644 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '手机验证码';
-- ----------------------------
-- Records of system_sms_code
@ -3344,7 +3345,7 @@ CREATE TABLE `system_sms_log` (
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '是否删除',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1234 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
) ENGINE = InnoDB AUTO_INCREMENT = 1238 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '短信日志';
-- ----------------------------
-- Records of system_sms_log
@ -3642,7 +3643,7 @@ CREATE TABLE `system_users` (
-- Records of system_users
-- ----------------------------
BEGIN;
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1,2]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/bf2002b38950c904243be7c825d3f82e29f25a44526583c3fde2ebdff3a87f75.png', 0, '0:0:0:0:0:0:0:1', '2024-11-08 19:27:07', 'admin', '2021-01-05 17:03:47', NULL, '2024-11-08 19:27:07', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (1, 'admin', '$2a$10$mRMIYLDtRHlf6.9ipiqH1.Z.bh/R9dO9d5iHiGYPigi6r5KOoR2Wm', '芋道源码', '管理员', 103, '[1,2]', 'aoteman@126.com', '18818260277', 2, 'http://test.yudao.iocoder.cn/bf2002b38950c904243be7c825d3f82e29f25a44526583c3fde2ebdff3a87f75.png', 0, '0:0:0:0:0:0:0:1', '2024-11-22 20:11:14', 'admin', '2021-01-05 17:03:47', NULL, '2024-11-22 20:11:14', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (100, 'yudao', '$2a$04$IgUse/ibRzAZ3rngCThmtemJeoh15Ux1TQ2hIMe4iwt/K3LcFHEda', '芋道', '不要吓我', 104, '[1]', 'yudao@iocoder.cn', '15601691300', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-11-02 14:00:46', '', '2021-01-07 09:07:17', NULL, '2024-11-02 14:00:46', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (103, 'yuanma', '$2a$04$fUBSmjKCPYAUmnMzOb6qE.eZCGPhHi1JmAKclODbfS/O7fHOl2bH6', '源码', NULL, 106, NULL, 'yuanma@iocoder.cn', '15601701300', 0, '', 0, '0:0:0:0:0:0:0:1', '2024-08-11 17:48:12', '', '2021-01-13 23:50:35', NULL, '2024-08-11 17:48:12', b'0', 1);
INSERT INTO `system_users` (`id`, `username`, `password`, `nickname`, `remark`, `dept_id`, `post_ids`, `email`, `mobile`, `sex`, `avatar`, `status`, `login_ip`, `login_date`, `creator`, `create_time`, `updater`, `update_time`, `deleted`, `tenant_id`) VALUES (104, 'test', '$2a$04$jDFLttgfik0QqJKAbfhMa.2A9xXoZmAIxakdFJUzkX.MgBKT6ddo6', '测试号', NULL, 107, '[1,2]', '111@qq.com', '15601691200', 1, '', 0, '0:0:0:0:0:0:0:1', '2024-09-17 15:05:43', '', '2021-01-21 02:13:53', NULL, '2024-09-17 15:05:43', b'0', 1);

View File

@ -1126,6 +1126,55 @@ INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_t
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1534, 1, '赢单', '1', 'crm_business_end_status_type', 0, 'success', '', '', '1', '2024-04-13 23:26:57', '1', '2024-04-13 23:26:57', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1535, 2, '输单', '2', 'crm_business_end_status_type', 0, 'primary', '', '', '1', '2024-04-13 23:27:31', '1', '2024-04-13 23:27:31', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1536, 3, '无效', '3', 'crm_business_end_status_type', 0, 'info', '', '', '1', '2024-04-13 23:27:59', '1', '2024-04-13 23:27:59', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1537, 1, 'OpenAI', 'OpenAI', 'ai_platform', 0, '', '', '', '1', '2024-05-09 22:33:47', '1', '2024-05-09 22:58:46', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1538, 2, 'Ollama', 'Ollama', 'ai_platform', 0, '', '', '', '1', '2024-05-17 23:02:55', '1', '2024-05-17 23:02:55', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1539, 3, '文心一言', 'YiYan', 'ai_platform', 0, '', '', '', '1', '2024-05-18 09:24:20', '1', '2024-05-18 09:29:01', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1540, 4, '讯飞星火', 'XingHuo', 'ai_platform', 0, '', '', '', '1', '2024-05-18 10:08:56', '1', '2024-05-18 10:08:56', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1541, 5, '通义千问', 'TongYi', 'ai_platform', 0, '', '', '', '1', '2024-05-18 10:32:29', '1', '2024-07-06 15:42:29', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1542, 6, 'StableDiffusion', 'StableDiffusion', 'ai_platform', 0, '', '', '', '1', '2024-06-01 15:09:31', '1', '2024-06-01 15:10:25', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1543, 10, '进行中', '10', 'ai_image_status', 0, 'primary', '', '', '1', '2024-06-26 20:51:41', '1', '2024-06-26 20:52:48', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1544, 20, '已完成', '20', 'ai_image_status', 0, 'success', '', '', '1', '2024-06-26 20:52:07', '1', '2024-06-26 20:52:41', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1545, 30, '已失败', '30', 'ai_image_status', 0, 'warning', '', '', '1', '2024-06-26 20:52:25', '1', '2024-06-26 20:52:35', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1546, 7, 'Midjourney', 'Midjourney', 'ai_platform', 0, '', '', '', '1', '2024-06-26 22:14:46', '1', '2024-06-26 22:14:46', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1547, 10, '进行中', '10', 'ai_music_status', 0, 'primary', '', '', '1', '2024-06-27 22:45:22', '1', '2024-06-28 00:56:17', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1548, 20, '已完成', '20', 'ai_music_status', 0, 'success', '', '', '1', '2024-06-27 22:45:33', '1', '2024-06-28 00:56:18', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1549, 30, '已失败', '30', 'ai_music_status', 0, 'danger', '', '', '1', '2024-06-27 22:45:44', '1', '2024-06-28 00:56:19', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1550, 1, '歌词模式', '1', 'ai_generate_mode', 0, '', '', '', '1', '2024-06-27 22:46:31', '1', '2024-06-28 01:22:25', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1551, 2, '描述模式', '2', 'ai_generate_mode', 0, '', '', '', '1', '2024-06-27 22:46:37', '1', '2024-06-28 01:22:24', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1552, 8, 'Suno', 'Suno', 'ai_platform', 0, '', '', '', '1', '2024-06-29 09:13:36', '1', '2024-06-29 09:13:41', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1553, 9, 'DeepSeek', 'DeepSeek', 'ai_platform', 0, '', '', '', '1', '2024-07-06 12:04:30', '1', '2024-07-06 12:05:20', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1554, 10, '智谱', 'ZhiPu', 'ai_platform', 0, '', '', '', '1', '2024-07-06 18:00:35', '1', '2024-07-06 18:00:35', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1555, 4, '', '4', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:49:03', '1', '2024-07-07 15:49:03', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1556, 5, '段落', '5', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:49:54', '1', '2024-07-07 15:49:54', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1557, 6, '文章', '6', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:05', '1', '2024-07-07 15:50:05', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1558, 7, '博客文章', '7', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:23', '1', '2024-07-07 15:50:23', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1559, 8, '想法', '8', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:31', '1', '2024-07-07 15:50:31', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1560, 9, '大纲', '9', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:50:37', '1', '2024-07-07 15:50:37', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1561, 1, '自动', '1', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:06', '1', '2024-07-07 15:51:06', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1562, 2, '友善', '2', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:19', '1', '2024-07-07 15:51:19', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1563, 3, '随意', '3', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:27', '1', '2024-07-07 15:51:27', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1564, 4, '友好', '4', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:37', '1', '2024-07-07 15:51:37', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1565, 5, '专业', '5', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:51:49', '1', '2024-07-07 15:52:02', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1566, 6, '诙谐', '6', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:52:15', '1', '2024-07-07 15:52:15', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1567, 7, '有趣', '7', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:52:24', '1', '2024-07-07 15:52:24', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1568, 8, '正式', '8', 'ai_write_tone', 0, '', '', '', '1', '2024-07-07 15:54:33', '1', '2024-07-07 15:54:33', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1569, 5, '段落', '5', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:49:54', '1', '2024-07-07 15:49:54', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1570, 1, '自动', '1', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:19:34', '1', '2024-07-07 15:19:34', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1571, 2, '电子邮件', '2', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:19:50', '1', '2024-07-07 15:49:30', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1572, 3, '消息', '3', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:20:01', '1', '2024-07-07 15:49:38', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1573, 4, '评论', '4', 'ai_write_format', 0, '', '', '', '1', '2024-07-07 15:20:13', '1', '2024-07-07 15:49:45', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1574, 1, '自动', '1', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:44:18', '1', '2024-07-07 15:44:18', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1575, 2, '中文', '2', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:44:28', '1', '2024-07-07 15:44:28', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1576, 3, '英文', '3', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:44:37', '1', '2024-07-07 15:44:37', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1577, 4, '韩语', '4', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:46:28', '1', '2024-07-07 15:46:28', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1578, 5, '日语', '5', 'ai_write_language', 0, '', '', '', '1', '2024-07-07 15:46:44', '1', '2024-07-07 15:46:44', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1579, 1, '自动', '1', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:48:34', '1', '2024-07-07 15:48:34', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1580, 2, '', '2', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:48:44', '1', '2024-07-07 15:48:44', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1581, 3, '中等', '3', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:48:52', '1', '2024-07-07 15:48:52', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1582, 4, '', '4', 'ai_write_length', 0, '', '', '', '1', '2024-07-07 15:49:03', '1', '2024-07-07 15:49:03', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1584, 1, '撰写', '1', 'ai_write_type', 0, '', '', '', '1', '2024-07-10 21:26:00', '1', '2024-07-10 21:26:00', '0');
INSERT INTO system_dict_data (id, sort, label, value, dict_type, status, color_type, css_class, remark, creator, create_time, updater, update_time, deleted) VALUES (1585, 2, '回复', '2', 'ai_write_type', 0, '', '', '', '1', '2024-07-10 21:26:06', '1', '2024-07-10 21:26:06', '0');
COMMIT;
-- @formatter:on
@ -2328,6 +2377,54 @@ INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2755, '删除项目', 'report:go-view-project:delete', 3, 2, 2153, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 20:01:37', '1', '2024-04-24 20:01:37', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2756, '会员等级记录查询', 'member:level-record:query', 3, 10, 2325, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 20:02:32', '1', '2024-04-24 20:02:32', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2757, '会员经验记录查询', 'member:experience-record:query', 3, 11, 2325, '', '', '', '', 0, '1', '1', '1', '1', '2024-04-24 20:02:51', '1', '2024-04-24 20:02:51', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2758, 'AI 大模型', '', 1, 400, 0, '/ai', 'fa:apple', '', '', 0, '1', '1', '1', '1', '2024-05-07 15:07:56', '1', '2024-05-25 12:36:12', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2759, 'AI 对话', '', 2, 1, 2758, 'chat', 'ep:message', 'ai/chat/index/index.vue', 'AiChat', 0, '1', '1', '1', '1', '2024-05-07 15:09:14', '1', '2024-07-07 17:15:36', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2760, '控制台', '', 1, 100, 2758, 'console', 'ep:setting', '', '', 0, '1', '1', '1', '1', '2024-05-09 22:39:09', '1', '2024-05-24 23:34:21', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2761, 'API 密钥', '', 2, 0, 2760, 'api-key', 'ep:key', 'ai/model/apiKey/index.vue', 'AiApiKey', 0, '1', '1', '1', '', '2024-05-09 14:52:56', '1', '2024-05-10 22:44:08', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2762, 'API 密钥查询', 'ai:api-key:query', 3, 1, 2761, '', '', '', '', 0, '1', '1', '1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:32', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2763, 'API 密钥创建', 'ai:api-key:create', 3, 2, 2761, '', '', '', '', 0, '1', '1', '1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:26', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2764, 'API 密钥更新', 'ai:api-key:update', 3, 3, 2761, '', '', '', '', 0, '1', '1', '1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:42', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2765, 'API 密钥删除', 'ai:api-key:delete', 3, 4, 2761, '', '', '', '', 0, '1', '1', '1', '', '2024-05-09 14:52:56', '1', '2024-05-13 20:36:48', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2767, '聊天模型', '', 2, 0, 2760, 'chat-model', 'fa-solid:abacus', 'ai/model/chatModel/index.vue', 'AiChatModel', 0, '1', '1', '1', '', '2024-05-10 14:42:48', '1', '2024-05-10 22:44:16', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2768, '聊天模型查询', 'ai:chat-model:query', 3, 1, 2767, '', '', '', '', 0, '1', '1', '1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:02', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2769, '聊天模型创建', 'ai:chat-model:create', 3, 2, 2767, '', '', '', '', 0, '1', '1', '1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:12', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2770, '聊天模型更新', 'ai:chat-model:update', 3, 3, 2767, '', '', '', '', 0, '1', '1', '1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:18', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2771, '聊天模型删除', 'ai:chat-model:delete', 3, 4, 2767, '', '', '', '', 0, '1', '1', '1', '', '2024-05-10 14:42:48', '1', '2024-05-13 20:37:23', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2773, '聊天角色', '', 2, 0, 2760, 'chat-role', 'fa:user-secret', 'ai/model/chatRole/index.vue', 'AiChatRole', 0, '1', '1', '1', '', '2024-05-13 12:39:28', '1', '2024-05-13 20:41:45', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2774, '聊天角色查询', 'ai:chat-role:query', 3, 1, 2773, '', '', '', NULL, 0, '1', '1', '1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2775, '聊天角色创建', 'ai:chat-role:create', 3, 2, 2773, '', '', '', NULL, 0, '1', '1', '1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2776, '聊天角色更新', 'ai:chat-role:update', 3, 3, 2773, '', '', '', NULL, 0, '1', '1', '1', '', '2024-05-13 12:39:28', '', '2024-05-13 12:39:28', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2777, '聊天角色删除', 'ai:chat-role:delete', 3, 4, 2773, '', '', '', '', 0, '1', '1', '1', '1', '2024-05-13 21:43:38', '1', '2024-05-13 21:43:38', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2778, '聊天管理', '', 2, 10, 2760, 'chat-conversation', 'ep:chat-square', 'ai/chat/manager/index.vue', 'AiChatManager', 0, '1', '1', '1', '', '2024-05-24 15:39:18', '1', '2024-06-26 21:36:56', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2779, '会话查询', 'ai:chat-conversation:query', 3, 1, 2778, '', '', '', '', 0, '1', '1', '1', '', '2024-05-24 15:39:18', '1', '2024-05-25 08:38:30', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2780, '会话删除', 'ai:chat-conversation:delete', 3, 2, 2778, '', '', '', '', 0, '1', '1', '1', '', '2024-05-24 15:39:18', '1', '2024-05-25 08:38:40', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2781, '消息查询', 'ai:chat-message:query', 3, 11, 2778, '', '', '', '', 0, '1', '1', '1', '1', '2024-05-25 08:38:56', '1', '2024-05-25 08:38:56', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2782, '消息删除', 'ai:chat-message:delete', 3, 12, 2778, '', '', '', '', 0, '1', '1', '1', '1', '2024-05-25 08:39:10', '1', '2024-05-25 08:39:10', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2783, 'AI 绘画', '', 2, 2, 2758, 'image', 'ep:picture-rounded', 'ai/image/index/index.vue', 'AiImage', 0, '1', '1', '1', '1', '2024-05-26 11:45:17', '1', '2024-07-07 17:18:59', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2784, '绘画管理', '', 2, 11, 2760, 'image', 'fa:file-image-o', 'ai/image/manager/index.vue', 'AiImageManager', 0, '1', '1', '1', '', '2024-06-26 13:32:31', '1', '2024-06-26 21:37:13', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2785, '绘画查询', 'ai:image:query', 3, 1, 2784, '', '', '', '', 0, '1', '1', '1', '', '2024-06-26 13:32:31', '1', '2024-06-26 22:21:57', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2786, '绘画删除', 'ai:image:delete', 3, 4, 2784, '', '', '', '', 0, '1', '1', '1', '', '2024-06-26 13:32:31', '1', '2024-06-26 22:22:08', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2787, '绘图更新', 'ai:image:update', 3, 2, 2784, '', '', '', '', 0, '1', '1', '1', '1', '2024-06-26 22:47:56', '1', '2024-08-31 09:21:35', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2788, '音乐管理', '', 2, 12, 2760, 'music', 'fa:music', 'ai/music/manager/index.vue', 'AiMusicManager', 0, '1', '1', '1', '', '2024-06-27 15:03:33', '1', '2024-06-27 23:04:19', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2789, '音乐查询', 'ai:music:query', 3, 1, 2788, '', '', '', NULL, 0, '1', '1', '1', '', '2024-06-27 15:03:33', '', '2024-06-27 15:03:33', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2790, '音乐更新', 'ai:music:update', 3, 3, 2788, '', '', '', NULL, 0, '1', '1', '1', '', '2024-06-27 15:03:33', '', '2024-06-27 15:03:33', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2791, '音乐删除', 'ai:music:delete', 3, 4, 2788, '', '', '', NULL, 0, '1', '1', '1', '', '2024-06-27 15:03:33', '', '2024-06-27 15:03:33', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2792, 'AI 写作', '', 2, 3, 2758, 'write', 'fa-solid:book-reader', 'ai/write/index/index.vue', 'AiWrite', 0, '1', '1', '1', '1', '2024-07-08 09:26:44', '1', '2024-07-16 13:03:06', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2793, '写作管理', '', 2, 13, 2760, 'write', 'fa:bookmark-o', 'ai/write/manager/index.vue', 'AiWriteManager', 0, '1', '1', '1', '', '2024-07-10 13:24:34', '1', '2024-07-10 21:31:59', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2794, 'AI 写作查询', 'ai:write:query', 3, 1, 2793, '', '', '', NULL, 0, '1', '1', '1', '', '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2795, 'AI 写作删除', 'ai:write:delete', 3, 4, 2793, '', '', '', NULL, 0, '1', '1', '1', '', '2024-07-10 13:24:34', '', '2024-07-10 13:24:34', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2796, 'AI 音乐', '', 2, 4, 2758, 'music', 'fa:music', 'ai/music/index/index.vue', 'AiMusic', 0, '1', '1', '1', '1', '2024-07-17 09:21:12', '1', '2024-07-29 21:11:52', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2797, '客服中心', '', 2, 100, 2362, 'kefu', 'fa-solid:user-alt', 'mall/promotion/kefu/index', 'KeFu', 0, '1', '1', '1', '1', '2024-07-17 23:49:05', '1', '2024-07-17 23:49:16', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2798, 'AI 思维导图', '', 2, 5, 2758, 'mind-map', 'fa:sitemap', 'ai/mindmap/index/index.vue', 'AiMindMap', 0, '1', '1', '1', '1', '2024-07-29 21:31:59', '1', '2024-07-29 21:33:20', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2799, '导图管理', '', 2, 14, 2760, 'mind-map', 'fa:map', 'ai/mindmap/manager/index', 'AiMindMapManager', 0, '1', '1', '1', '', '2024-08-10 09:15:09', '1', '2024-08-10 17:24:28', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2800, '思维导图查询', 'ai:mind-map:query', 3, 1, 2799, '', '', '', NULL, 0, '1', '1', '1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2801, '思维导图删除', 'ai:mind-map:delete', 3, 4, 2799, '', '', '', NULL, 0, '1', '1', '1', '', '2024-08-10 09:15:09', '', '2024-08-10 09:15:09', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2802, '会话查询', 'promotion:kefu-conversation:query', 3, 1, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:17:52', '1', '2024-08-31 09:18:52', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2803, '会话更新', 'promotion:kefu-conversation:update', 3, 2, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:18:15', '1', '2024-08-31 09:19:29', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2804, '消息查询', 'promotion:kefu-message:query', 3, 10, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:18:42', '1', '2024-08-31 09:18:42', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2805, '会话删除', 'promotion:kefu-conversation:delete', 3, 3, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:19:51', '1', '2024-08-31 09:20:32', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2806, '消息发送', 'promotion:kefu-message:send', 3, 12, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:20:06', '1', '2024-08-31 09:20:06', '0');
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted) VALUES (2807, '消息更新', 'promotion:kefu-message:update', 3, 11, 2797, '', '', '', '', 0, '1', '1', '1', '1', '2024-08-31 09:20:22', '1', '2024-08-31 09:20:22', '0');
COMMIT;
-- @formatter:on

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.framework.common.util.collection;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import com.google.common.collect.ImmutableMap;
import java.util.*;
@ -73,6 +74,13 @@ public class CollectionUtils {
return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());
}
public static <T, U> PageResult<U> convertPage(PageResult<T> from, Function<T, U> func) {
if (ArrayUtil.isEmpty(from)) {
return new PageResult<>(from.getTotal());
}
return new PageResult<>(convertList(from.getList(), func), from.getTotal());
}
public static <T, U> List<U> convertListByFlatMap(Collection<T> from,
Function<T, ? extends Stream<? extends U>> func) {
if (CollUtil.isEmpty(from)) {
@ -324,7 +332,7 @@ public class CollectionUtils {
}
public static <T> List<T> newArrayList(List<List<T>> list) {
return list.stream().flatMap(Collection::stream).collect(Collectors.toList());
return list.stream().filter(Objects::nonNull).flatMap(Collection::stream).collect(Collectors.toList());
}
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.tenant.config.TenantProperties;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
import com.baomidou.mybatisplus.extension.toolkit.SqlParserUtils;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
@ -37,7 +38,7 @@ public class TenantDatabaseInterceptor implements TenantLineHandler {
@Override
public boolean ignoreTable(String tableName) {
return TenantContextHolder.isIgnore() // 情况一,全局忽略多租户
|| CollUtil.contains(ignoreTables, tableName); // 情况二,忽略多租户的表
|| CollUtil.contains(ignoreTables, SqlParserUtils.removeWrapperSymbol(tableName)); // 情况二,忽略多租户的表
}
}

View File

@ -36,8 +36,9 @@ public class MyBatisUtils {
Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());
// 排序字段
if (!CollectionUtil.isEmpty(sortingFields)) {
page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) ?
OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))
page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder())
? OrderItem.asc(StrUtil.toUnderlineCase(sortingField.getField()))
: OrderItem.desc(StrUtil.toUnderlineCase(sortingField.getField())))
.collect(Collectors.toList()));
}
return page;

View File

@ -17,7 +17,7 @@ public abstract class AbstractSliderDesensitizationHandler<T extends Annotation>
public String desensitize(String origin, T annotation) {
// 1. 判断是否禁用脱敏
Object disable = SpringExpressionUtils.parseExpression(getDisable(annotation));
if (Boolean.FALSE.equals(disable)) {
if (Boolean.TRUE.equals(disable)) {
return origin;
}

View File

@ -64,8 +64,10 @@ public abstract class AbstractWebSocketMessageSender implements WebSocketMessage
sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType);
}
if (CollUtil.isEmpty(sessions)) {
log.info("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]",
sessionId, userType, userId, messageType, messageContent);
if (log.isDebugEnabled()) {
log.debug("[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]",
sessionId, userType, userId, messageType, messageContent);
}
}
// 2. 执行发送
doSend(sessions, messageType, messageContent);

View File

@ -222,8 +222,8 @@ knife4j:
service-name: crm-server
url: /admin-api/crm/v3/api-docs
- name: ai-server
service-name: crm-server
url: /admin-api/crm/v3/api-docs
service-name: ai-server
url: /admin-api/ai/v3/api-docs
--- #################### 芋道相关配置 ####################

View File

@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
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.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.*;
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyActionReqVO;
import cn.iocoder.yudao.module.ai.controller.admin.image.vo.midjourney.AiMidjourneyImagineReqVO;
@ -95,6 +96,7 @@ public class AiImageController {
@Operation(summary = "【Midjourney】通知图片进展", description = "由 Midjourney Proxy 回调")
@PostMapping("/midjourney/notify") // 必须是 POST 方法,否则会报错
@PermitAll
@TenantIgnore
public CommonResult<Boolean> midjourneyNotify(@Valid @RequestBody MidjourneyApi.Notify notify) {
imageService.midjourneyNotify(notify);
return success(true);

View File

@ -23,7 +23,7 @@ public interface ErrorCodeConstants {
"原因:用户任务({})未配置审批人,请点击【流程设计】按钮,选择该它的【任务(审批人)】进行配置");
ErrorCode MODEL_DEPLOY_FAIL_BPMN_START_EVENT_NOT_EXISTS = new ErrorCode(1_009_002_005, "部署流程失败原因BPMN 流程图中,没有开始事件");
ErrorCode MODEL_DEPLOY_FAIL_BPMN_USER_TASK_NAME_NOT_EXISTS = new ErrorCode(1_009_002_006, "部署流程失败原因BPMN 流程图中,用户任务({})的名字不存在");
ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程的管理员");
ErrorCode MODEL_UPDATE_FAIL_NOT_MANAGER = new ErrorCode(1_009_002_007, "操作流程失败,原因:你不是该流程({})的管理员");
// ========== 流程定义 1-009-003-000 ==========
ErrorCode PROCESS_DEFINITION_KEY_NOT_MATCH = new ErrorCode(1_009_003_000, "流程定义的标识期望是({}),当前是({}),请修改 BPMN 流程图");
@ -35,8 +35,8 @@ public interface ErrorCodeConstants {
ErrorCode PROCESS_INSTANCE_NOT_EXISTS = new ErrorCode(1_009_004_000, "流程实例不存在");
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_EXISTS = new ErrorCode(1_009_004_001, "流程取消失败,流程不处于运行中");
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_SELF = new ErrorCode(1_009_004_002, "流程取消失败,该流程不是你发起的");
ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "审批任务({})的审批人未配置");
ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "审批任务({})的审批人({})不存在");
ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_003, "任务({})的候选人未配置");
ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
// ========== 流程任务 1-009-005-000 ==========
@ -44,7 +44,7 @@ public interface ErrorCodeConstants {
ErrorCode TASK_NOT_EXISTS = new ErrorCode(1_009_005_002, "流程任务不存在");
ErrorCode TASK_IS_PENDING = new ErrorCode(1_009_005_003, "当前任务处于挂起状态,不能操作");
ErrorCode TASK_TARGET_NODE_NOT_EXISTS = new ErrorCode(1_009_005_004, " 目标节点不存在");
ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
ErrorCode TASK_RETURN_FAIL_SOURCE_TARGET_ERROR = new ErrorCode(1_009_005_006, "退任务失败,目标节点是在并行网关上或非同一路线上,不可跳转");
ErrorCode TASK_DELEGATE_FAIL_USER_REPEAT = new ErrorCode(1_009_005_007, "任务委派失败,委派人和当前审批人为同一人");
ErrorCode TASK_DELEGATE_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_008, "任务委派失败,被委派人不存在");
ErrorCode TASK_SIGN_CREATE_USER_NOT_EXIST = new ErrorCode(1_009_005_009, "任务加签:选择的用户不存在");

View File

@ -18,30 +18,26 @@ import java.util.Objects;
public enum BpmSimpleModelNodeType implements IntArrayValuable {
// 0 ~ 1 开始和结束
START_NODE(0, "startEvent", "开始节点"),
END_NODE(1, "endEvent", "结束节点"),
START_NODE(0, "开始", "startEvent"),
END_NODE(1, "结束", "endEvent"),
// 10 ~ 49 各种节点
START_USER_NODE(10, "userTask", "发起人节点"), // 发起人节点。前端的开始节点Id 固定
APPROVE_NODE(11, "userTask", "审批人节点"),
COPY_NODE(12, "serviceTask", "抄送人节点"),
START_USER_NODE(10, "发起人", "userTask"), // 发起人节点。前端的开始节点Id 固定
APPROVE_NODE(11, "审批人", "userTask"),
COPY_NODE(12, "抄送人", "serviceTask"),
// 50 ~ 条件分支
CONDITION_NODE(50, "sequenceFlow", "条件节点"), // 用于构建流转条件的表达式
CONDITION_BRANCH_NODE(51, " “parallelGateway”", "条件分支节点"), // TODO @jason是不是改成叫 条件分支?
PARALLEL_BRANCH_NODE(52, "exclusiveGateway", "并行分支节点"), // TODO @jason是不是一个 并行分支 ?就可以啦? 后面是否去掉并行网关。只用包容网关
INCLUSIVE_BRANCH_NODE(53, "inclusiveGateway", "包容分支节点"),
// TODO @jason建议整合 join最终只有 条件分支、并行分支、包容分支,三种~
// TODO @芋艿。 感觉还是分开好理解一点,也好处理一点。前端结构中把聚合节点显示并传过来。
CONDITION_NODE(50, "条件", "sequenceFlow"), // 用于构建流转条件的表达式
CONDITION_BRANCH_NODE(51, "条件分支", "exclusiveGateway"),
PARALLEL_BRANCH_NODE(52, "并行分支", "parallelGateway"),
INCLUSIVE_BRANCH_NODE(53, "包容分支", "inclusiveGateway"),
;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray();
public static final String BPMN_USER_TASK_TYPE = "userTask";
private final Integer type;
private final String bpmnType;
private final String name;
private final String bpmnType;
/**
*
@ -54,16 +50,6 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable {
|| Objects.equals(INCLUSIVE_BRANCH_NODE.getType(), type);
}
/**
*
*
* @param bpmnType bpmn
*/
public static boolean isRecordNode(String bpmnType) {
return Objects.equals(APPROVE_NODE.getBpmnType(), bpmnType)
|| Objects.equals(END_NODE.getBpmnType(), bpmnType);
}
public static BpmSimpleModelNodeType valueOf(Integer type) {
return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values());
}

View File

@ -16,20 +16,23 @@ import java.util.Arrays;
@AllArgsConstructor
public enum BpmUserTaskApproveMethodEnum implements IntArrayValuable {
RANDOM(1, "随机挑选一人审批"),
RATIO(2, "多人会签(按通过比例)"), // 会签(按通过比例)
ANY(3, "多人或签(一人通过或拒绝)"), // 或签(通过只需一人,拒绝只需一人)
SEQUENTIAL(4, "依次审批"); // 依次审批
RANDOM(1, "随机挑选一人审批", null),
RATIO(2, "多人会签(按通过比例)", "${ nrOfCompletedInstances/nrOfInstances >= %s}"), // 会签(按通过比例)
ANY(3, "多人或签(一人通过或拒绝)", "${ nrOfCompletedInstances > 0 }"), // 或签(通过只需一人,拒绝只需一人)
SEQUENTIAL(4, "依次审批", "${ nrOfCompletedInstances >= nrOfInstances }"); // 依次审批
/**
*
*/
private final Integer method;
/**
*
*/
private final String name;
/**
*
*/
private final String completionCondition;
public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmUserTaskApproveMethodEnum::getMethod).toArray();

View File

@ -38,6 +38,10 @@ public enum BpmProcessInstanceStatusEnum implements IntArrayValuable {
return ARRAYS;
}
public static boolean isRejectStatus(Integer status) {
return REJECT.getStatus().equals(status);
}
public static boolean isProcessEndStatus(Integer status) {
return ObjectUtils.equalsAny(status,
APPROVE.getStatus(), REJECT.getStatus(), CANCEL.getStatus());

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.enums.task;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import lombok.AllArgsConstructor;
import lombok.Getter;
@ -20,7 +21,6 @@ public enum BpmTaskStatusEnum {
CANCEL(4, "已取消"),
RETURN(5, "已退回"),
DELEGATE(6, "委派中"),
/**
* 使
@ -45,6 +45,10 @@ public enum BpmTaskStatusEnum {
*/
private final String name;
public static boolean isRejectStatus(Integer status) {
return REJECT.getStatus().equals(status);
}
/**
* End
* <p>
@ -59,4 +63,8 @@ public enum BpmTaskStatusEnum {
RETURN.getStatus(), APPROVING.getStatus());
}
public static boolean isCancelStatus(Integer status) {
return ObjUtil.equal(status, CANCEL.getStatus());
}
}

View File

@ -9,11 +9,14 @@ public class UserSimpleBaseVO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String nickname;
@Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
private String avatar;
@Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long deptId;
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部")
private String deptName;
}

View File

@ -48,6 +48,15 @@ public class BpmCategoryController {
return success(true);
}
@PutMapping("/update-sort-batch")
@Operation(summary = "批量更新流程分类的排序")
@Parameter(name = "ids", description = "分类编号列表", required = true, example = "1,2,3")
@PreAuthorize("@ss.hasPermission('bpm:category:update')")
public CommonResult<Boolean> updateCategorySortBatch(@RequestParam("ids") List<Long> ids) {
categoryService.updateCategorySortBatch(ids);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除流程分类")
@Parameter(name = "id", description = "编号", required = true)

View File

@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
@ -19,6 +17,8 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
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.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.Model;
import org.flowable.engine.repository.ProcessDefinition;
@ -26,9 +26,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.HashSet;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -56,38 +54,38 @@ public class BpmModelController {
@Resource
private AdminUserApi adminUserApi;
@GetMapping("/page")
@GetMapping("/list")
@Operation(summary = "获得模型分页")
public CommonResult<PageResult<BpmModelRespVO>> getModelPage(BpmModelPageReqVO pageVO) {
PageResult<Model> pageResult = modelService.getModelPage(pageVO);
if (CollUtil.isEmpty(pageResult.getList())) {
return success(PageResult.empty(pageResult.getTotal()));
@Parameter(name = "name", description = "模型名称", example = "芋艿")
public CommonResult<List<BpmModelRespVO>> getModelPage(@RequestParam(value = "name", required = false) String name) {
List<Model> list = modelService.getModelList(name);
if (CollUtil.isEmpty(list)) {
return success(Collections.emptyList());
}
// 拼接数据
// 获得 Form 表单
Set<Long> formIds = convertSet(pageResult.getList(), model -> {
Set<Long> formIds = convertSet(list, model -> {
BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
return metaInfo != null ? metaInfo.getFormId() : null;
});
Map<Long, BpmFormDO> formMap = formService.getFormMap(formIds);
// 获得 Category Map
Map<String, BpmCategoryDO> categoryMap = categoryService.getCategoryMap(
convertSet(pageResult.getList(), Model::getCategory));
convertSet(list, Model::getCategory));
// 获得 Deployment Map
Set<String> deploymentIds = new HashSet<>();
pageResult.getList().forEach(model -> CollectionUtils.addIfNotNull(deploymentIds, model.getDeploymentId()));
Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(deploymentIds);
Map<String, Deployment> deploymentMap = processDefinitionService.getDeploymentMap(
convertSet(list, Model::getDeploymentId));
// 获得 ProcessDefinition Map
List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(deploymentIds);
List<ProcessDefinition> processDefinitions = processDefinitionService.getProcessDefinitionListByDeploymentIds(
deploymentMap.keySet());
Map<String, ProcessDefinition> processDefinitionMap = convertMap(processDefinitions, ProcessDefinition::getDeploymentId);
// 获得 User Map
Set<Long> userIds = convertSetByFlatMap(pageResult.getList(), model -> {
Set<Long> userIds = convertSetByFlatMap(list, model -> {
BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
return metaInfo != null ? metaInfo.getStartUserIds().stream() : Stream.empty();
});
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
return success(BpmModelConvert.INSTANCE.buildModelPage(pageResult,
return success(BpmModelConvert.INSTANCE.buildModelList(list,
formMap, categoryMap, deploymentMap, processDefinitionMap, userMap));
}
@ -111,6 +109,7 @@ public class BpmModelController {
return success(modelService.createModel(createRetVO));
}
@PutMapping("/update")
@Operation(summary = "修改模型")
@PreAuthorize("@ss.hasPermission('bpm:model:update')")
@ -119,6 +118,14 @@ public class BpmModelController {
return success(true);
}
@PutMapping("/update-sort-batch")
@Operation(summary = "批量修改模型排序")
@Parameter(name = "ids", description = "编号数组", required = true, example = "1,2,3")
public CommonResult<Boolean> updateModelSortBatch(@RequestParam("ids") List<String> ids) {
modelService.updateModelSortBatch(getLoginUserId(), ids);
return success(true);
}
@PostMapping("/deploy")
@Operation(summary = "部署模型")
@Parameter(name = "id", description = "编号", required = true, example = "1024")

View File

@ -9,7 +9,6 @@ import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConver
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateStartUserSelectStrategy;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
@ -17,7 +16,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.security.access.prepost.PreAuthorize;
@ -115,9 +113,8 @@ public class BpmProcessDefinitionController {
}
BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(processDefinition.getId());
BpmnModel bpmnModel = processDefinitionService.getProcessDefinitionBpmnModel(processDefinition.getId());
List<UserTask> userTaskList = BpmTaskCandidateStartUserSelectStrategy.getStartUserSelectUserTaskList(bpmnModel);
return success(BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
processDefinition, null, processDefinitionInfo, null, null, bpmnModel, userTaskList));
processDefinition, null, processDefinitionInfo, null, null, bpmnModel));
}
}

View File

@ -15,7 +15,9 @@ import java.util.List;
* BPM MetaInfo Response DTO
* { Model#setMetaInfo(String)}
*
* {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO}
*
* {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO}
*
*
* @author
*/
@ -40,13 +42,11 @@ public class BpmModelMetaInfoVO {
@NotNull(message = "表单类型不能为空")
private Integer formType;
@Schema(description = "表单编号", example = "1024")
private Long formId; // formType 为 NORMAL 使用,必须非空
@Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址",
example = "/bpm/oa/leave/create")
private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
@Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址",
example = "/bpm/oa/leave/view")
private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空
private Long formId; // formType 为 NORMAL 使用,必须非空
@Schema(description = "自定义表单的提交路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/create")
private String formCustomCreatePath; // 表单类型为 CUSTOM 时,必须非空
@Schema(description = "自定义表单的查看路径,使用 Vue 的路由地址", example = "/bpm/oa/leave/view")
private String formCustomViewPath; // 表单类型为 CUSTOM 时,必须非空
@Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否可见不能为空")
@ -59,4 +59,7 @@ public class BpmModelMetaInfoVO {
@NotEmpty(message = "可管理用户编号数组不能为空")
private List<Long> managerUserIds;
@Schema(description = "排序", example = "1")
private Long sort; // 创建时,后端自动生成
}

View File

@ -1,21 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 流程模型分页 Request VO")
@Data
public class BpmModelPageReqVO extends PageParam {
@Schema(description = "标识,精准匹配", example = "process1641042089407")
private String key;
@Schema(description = "名字,模糊匹配", example = "芋道")
private String name;
@Schema(description = "流程分类", example = "1")
private String category;
}

View File

@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.*;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -31,7 +30,6 @@ public class BpmSimpleModelNodeVO {
@Schema(description = "模型节点名称", example = "领导审批")
private String name;
// TODO @jason和 gpt 大模型对了下这个字段的命名,貌似叫 displayText 合适点。可以等最后我们全局替换下。(优先级:低)
@Schema(description = "节点展示内容", example = "指定成员: 芋道源码")
private String showText;
@ -79,12 +77,6 @@ public class BpmSimpleModelNodeVO {
@Schema(description = "操作按钮设置", example = "[]")
private List<OperationButtonSetting> buttonsSetting; // 用于审批节点
// TODO @jason看看是不是可以简化@芋艿: 暂时先放着。不知道后面是否会用到
/**
* Id, .
*/
@JsonIgnore
private String attachNodeId;
/**
*
*/

View File

@ -33,6 +33,9 @@ public class BpmProcessDefinitionRespVO {
@Schema(description = "流程分类名字", example = "请假")
private String categoryName;
@Schema(description = "流程模型的类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer modelType; // 参见 BpmModelTypeEnum 枚举类
@Schema(description = "表单类型-参见 bpm_model_form_type 数据字典", example = "1")
private Integer formType;
@Schema(description = "表单编号-在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时,必须非空", example = "1024")
@ -59,9 +62,12 @@ public class BpmProcessDefinitionRespVO {
@Schema(description = "BPMN XML")
private String bpmnXml; // 需要从对应的 BpmnModel 读取,非必须返回
@Schema(description = "发起用户需要选择审批人的任务数组")
private List<UserTask> startUserSelectTasks; // 需要从对应的 BpmnModel 读取,非必须返回
@Schema(description = "SIMPLE 设计器模型数据 json 格式")
private String simpleModel; // 非必须返回
@Schema(description = "流程定义排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long sort;
@Schema(description = "BPMN UserTask 用户任务")
@Data
public static class UserTask {

View File

@ -1,40 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
import cn.iocoder.yudao.module.bpm.convert.task.BpmActivityConvert;
import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 流程活动实例")
@RestController
@RequestMapping("/bpm/activity")
@Validated
public class BpmActivityController {
@Resource
private BpmActivityService activityService;
@GetMapping("/list")
@Operation(summary = "生成指定流程实例的高亮流程图",
description = "只高亮进行中的任务。不过要注意,该接口暂时没用,通过前端的 ProcessViewer.vue 界面的 highlightDiagram 方法生成")
@Parameter(name = "processInstanceId", description = "流程实例的编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
public CommonResult<List<BpmActivityRespVO>> getActivityList(
@RequestParam("processInstanceId") String processInstanceId) {
return success(BpmActivityConvert.INSTANCE.convertList(activityService.getActivityListByProcessInstanceId(processInstanceId)));
}
}

View File

@ -0,0 +1,16 @@
### 请求 /bpm/process-instance/get-bpmn 接口 => 成功
GET {{baseUrl}}/bpm/process-instance/get-bpmn-model-view?id=1d5fb5a6-85f8-11ef-b717-7e93075f94e3
Content-Type: application/json
tenant-id: 1
Authorization: Bearer {{token}}
### 请求 /bpm/process-instance/get-bpmn 接口 => 失败
#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=1d5fb5a6-85f8-11ef-b717-7e93075f94e3
#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=3ee5c5ba-904a-11ef-a76e-b2ed5d6ef911
#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=f630dfa2-8f92-11ef-947c-ba5e239a6eb4
#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=9de8bdbf-9133-11ef-ae97-eaf49df1f932
#GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processInstanceId=dd2188eb-9394-11ef-a039-7a9ac3d9eb6b
GET {{baseUrl}}/bpm/process-instance/get-approval-detail?processDefinitionId=test-auto:1:c70a799a-9394-11ef-a039-7a9ac3d9eb6b
Content-Type: application/json
tenant-id: 1
Authorization: Bearer {{token}}

View File

@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.*;
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.framework.flowable.core.util.BpmnModelUtils;
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;
@ -128,15 +127,13 @@ public class BpmProcessInstanceController {
processInstance.getProcessDefinitionId());
BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionService.getProcessDefinitionInfo(
processInstance.getProcessDefinitionId());
String bpmnXml = BpmnModelUtils.getBpmnXml(
processDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId()));
AdminUserRespDTO startUser = adminUserApi.getUser(NumberUtils.parseLong(processInstance.getStartUserId())).getCheckedData();
DeptRespDTO dept = null;
if (startUser != null && startUser.getDeptId() != null) {
dept = deptApi.getDept(startUser.getDeptId()).getCheckedData();
}
return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstance(processInstance,
processDefinition, processDefinitionInfo, bpmnXml, startUser, dept));
processDefinition, processDefinitionInfo, startUser, dept));
}
@DeleteMapping("/cancel-by-start-user")
@ -157,14 +154,6 @@ public class BpmProcessInstanceController {
return success(true);
}
@GetMapping("/get-form-fields-permission")
@Operation(summary = "获得表单字段权限")
@PreAuthorize("@ss.hasPermission('bpm:process-instance:query')")
public CommonResult<Map<String, String>> getFormFieldsPermission(
@Valid BpmFormFieldsPermissionReqVO reqVO) {
return success(processInstanceService.getFormFieldsPermission(reqVO));
}
@GetMapping("/get-approval-detail")
@Operation(summary = "获得审批详情")
@Parameter(name = "id", description = "流程实例的编号", required = true)
@ -173,4 +162,11 @@ public class BpmProcessInstanceController {
return success(processInstanceService.getApprovalDetail(getLoginUserId(), reqVO));
}
@GetMapping("/get-bpmn-model-view")
@Operation(summary = "获取流程实例的 BPMN 模型视图", description = "在【流程详细】界面中,进行调用")
@Parameter(name = "id", description = "流程实例的编号", required = true)
public CommonResult<BpmProcessInstanceBpmnModelViewRespVO> getProcessInstanceBpmnModelView(@RequestParam(value = "id") String id) {
return success(processInstanceService.getProcessInstanceBpmnModelView(id));
}
}

View File

@ -6,6 +6,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.cc.BpmProcessInstanceCopyRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
@ -28,8 +29,7 @@ import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 流程实例抄送")
@ -62,11 +62,15 @@ public class BpmProcessInstanceCopyController {
convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId));
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(),
copy -> Stream.of(copy.getStartUserId(), Long.parseLong(copy.getCreator()))));
return success(BeanUtils.toBean(pageResult, BpmProcessInstanceCopyRespVO.class, copyVO -> {
MapUtils.findAndThen(userMap, Long.valueOf(copyVO.getCreator()), user -> copyVO.setCreatorName(user.getNickname()));
MapUtils.findAndThen(userMap, copyVO.getStartUserId(), user -> copyVO.setStartUserName(user.getNickname()));
return success(convertPage(pageResult, copy -> {
BpmProcessInstanceCopyRespVO copyVO = BeanUtils.toBean(copy, BpmProcessInstanceCopyRespVO.class);
MapUtils.findAndThen(userMap, Long.valueOf(copy.getCreator()),
user -> copyVO.setStartUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
MapUtils.findAndThen(userMap, copy.getStartUserId(),
user -> copyVO.setCreateUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(),
processInstance -> copyVO.setProcessInstanceStartTime(DateUtils.of(processInstance.getStartTime())));
return copyVO;
}));
}

View File

@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.convert.task.BpmTaskConvert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.service.definition.BpmFormService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@ -18,7 +17,8 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.flowable.bpmn.model.BpmnModel;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
@ -28,8 +28,6 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -52,8 +50,6 @@ public class BpmTaskController {
private BpmProcessInstanceService processInstanceService;
@Resource
private BpmFormService formService;
@Resource
private BpmProcessDefinitionService bpmProcessDefinitionService;
@Resource
private AdminUserApi adminUserApi;
@ -121,27 +117,22 @@ public class BpmTaskController {
@PreAuthorize("@ss.hasPermission('bpm:task:query')")
public CommonResult<List<BpmTaskRespVO>> getTaskListByProcessInstanceId(
@RequestParam("processInstanceId") String processInstanceId) {
List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId);
List<HistoricTaskInstance> taskList = taskService.getTaskListByProcessInstanceId(processInstanceId, true);
if (CollUtil.isEmpty(taskList)) {
return success(Collections.emptyList());
}
// 拼接数据
HistoricProcessInstance processInstance = processInstanceService.getHistoricProcessInstance(processInstanceId);
// 获得 User 和 Dept Map
Set<Long> userIds = convertSetByFlatMap(taskList, task ->
Stream.of(NumberUtils.parseLong(task.getAssignee()), NumberUtils.parseLong(task.getOwner())));
userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
// 获得 Form Map
Map<Long, BpmFormDO> formMap = formService.getFormMap(
convertSet(taskList, task -> NumberUtils.parseLong(task.getFormKey())));
// 获得 BpmnModel
BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(processInstance.getProcessDefinitionId());
return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList, processInstance,
formMap, userMap, deptMap, bpmnModel));
return success(BpmTaskConvert.INSTANCE.buildTaskListByProcessInstanceId(taskList,
formMap, userMap, deptMap));
}
@PutMapping("/approve")
@ -161,7 +152,7 @@ public class BpmTaskController {
}
@GetMapping("/list-by-return")
@Operation(summary = "获取所有可退的节点", description = "用于【流程详情】的【退】按钮")
@Operation(summary = "获取所有可退的节点", description = "用于【流程详情】的【退】按钮")
@Parameter(name = "taskId", description = "当前任务ID", required = true)
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
public CommonResult<List<BpmTaskRespVO>> getTaskListByReturn(@RequestParam("id") String id) {
@ -171,7 +162,7 @@ public class BpmTaskController {
}
@PutMapping("/return")
@Operation(summary = "退任务", description = "用于【流程详情】的【退】按钮")
@Operation(summary = "退任务", description = "用于【流程详情】的【退】按钮")
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
public CommonResult<Boolean> returnTask(@Valid @RequestBody BpmTaskReturnReqVO reqVO) {
taskService.returnTask(getLoginUserId(), reqVO);
@ -210,6 +201,14 @@ public class BpmTaskController {
return success(true);
}
@PutMapping("/copy")
@Operation(summary = "抄送任务")
@PreAuthorize("@ss.hasPermission('bpm:task:update')")
public CommonResult<Boolean> copyTask(@Valid @RequestBody BpmTaskCopyReqVO reqVO) {
taskService.copyTask(getLoginUserId(), reqVO);
return success(true);
}
@GetMapping("/list-by-parent-task-id")
@Operation(summary = "获得指定父级任务的子任务列表") // 目前用于,减签的时候,获得子任务列表
@Parameter(name = "parentTaskId", description = "父级任务编号", required = true)

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.cc;
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -12,31 +13,31 @@ public class BpmProcessInstanceCopyRespVO {
@Schema(description = "抄送主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "发起人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
private Long startUserId;
@Schema(description = "发起人昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道")
private String startUserName;
@Schema(description = "发起人", requiredMode = Schema.RequiredMode.REQUIRED)
private UserSimpleBaseVO startUser;
@Schema(description = "流程实例编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "A233")
private String processInstanceId;
@Schema(description = "流程实例的名称")
@Schema(description = "流程实例的名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试")
private String processInstanceName;
@Schema(description = "流程实例的发起时间")
@Schema(description = "流程实例的发起时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime processInstanceStartTime;
@Schema(description = "抄送的节点的活动编号")
@Schema(description = "流程活动的编号", requiredMode = Schema.RequiredMode.REQUIRED)
private String activityId;
@Schema(description = "发起抄送的任务编号")
@Schema(description = "流程活动的名字", requiredMode = Schema.RequiredMode.REQUIRED)
private String activityName;
@Schema(description = "流程活动的编号")
private String taskId;
@Schema(description = "发起抄送的任务名称")
private String taskName;
@Schema(description = "抄送人")
private String creator;
@Schema(description = "抄送人昵称")
private String creatorName;
@Schema(description = "抄送人意见")
private String reason;
@Schema(description = "抄送时间")
@Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED)
private UserSimpleBaseVO createUser;
@Schema(description = "抄送时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@ -3,20 +3,30 @@ package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import lombok.Data;
import javax.validation.constraints.AssertTrue;
import java.util.Map;
// TODO @jason这个可以简化下使用 @RequestParam。嘿嘿主要 VO 项不要太多
@Schema(description = "管理后台 - 审批详情 Request VO")
@Data
public class BpmApprovalDetailReqVO {
@Schema(description = "流程定义的编号", example = "1024")
private String processDefinitionId;
private String processDefinitionId; // 使用场景:发起流程时,传流程定义 ID
@Schema(description = "流程变量")
private Map<String, Object> processVariables; // 使用场景:同 processDefinitionId用于流程预测
@Schema(description = "流程实例的编号", example = "1024")
private String processInstanceId;
private String processInstanceId; // 使用场景:流程已发起时候传流程实例 ID
// TODO @芋艿:如果未来 BPMN 增加流程图,它没有发起人节点,会有问题。
@Schema(description = "流程活动编号", example = "StartUserNode")
private String activityId; // 用于获取表单权限。1发起流程时传“发起人节点” activityId 可获取发起人的表单权限2从抄送列表界面进来时传抄送的 activityId 可获取抄送人的表单权限;
@Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b")
private String taskId; // 用于获取表单权限。1从待审批/已审批界面进来时,传递 taskId 任务编号,可获取任务节点的变得权限
@AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空")
@JsonIgnore

View File

@ -1,10 +1,15 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - 审批详情 Response VO")
@ -14,12 +19,28 @@ public class BpmApprovalDetailRespVO {
@Schema(description = "流程实例的状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status; // 参见 BpmProcessInstanceStatusEnum 枚举
@Schema(description = "审批信息列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ApprovalNodeInfo> approveNodes;
@Schema(description = "活动节点列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ActivityNode> activityNodes;
@Schema(description = "审批节点信息")
@Schema(description = "表单字段权限")
private Map<String, String> formFieldsPermission;
@Schema(description = "待办任务")
private BpmTaskRespVO todoTask;
/**
*
*/
private BpmProcessDefinitionRespVO processDefinition;
/**
*
*/
private BpmProcessInstanceRespVO processInstance;
@Schema(description = "活动节点信息")
@Data
public static class ApprovalNodeInfo {
public static class ActivityNode {
@Schema(description = "节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartUserNode")
private String id;
@ -39,42 +60,40 @@ public class BpmApprovalDetailRespVO {
private LocalDateTime endTime;
@Schema(description = "审批节点的任务信息")
private List<ApprovalTaskInfo> tasks;
private List<ActivityNodeTask> tasks;
@Schema(description = "候选人策略", example = "35")
private Integer candidateStrategy; // 参见 BpmTaskCandidateStrategyEnum 枚举。主要用于发起时,审批节点、抄送节点自选
@Schema(description = "候选人用户 ID 列表", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818")
@JsonIgnore // 不返回,只是方便后续读取,赋值给 candidateUsers
private List<Long> candidateUserIds;
@Schema(description = "候选人用户列表")
// TODO @jasoncandidateUserList => candidateUsers保持和 tasks 的命名风格一致哈
private List<User> candidateUserList; // 用于未运行任务节点
private List<UserSimpleBaseVO> candidateUsers; // 只包含未生成 ApprovalTaskInfo 的用户列表
}
// TODO @jason可以替换成 UserSimpleBaseVO。简化下
@Schema(description = "用户信息")
@Schema(description = "活动节点的任务信息")
@Data
public static class User {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String nickname;
@Schema(description = "用户头像", example = "https://www.iocoder.cn/1.png")
private String avatar;
}
@Schema(description = "审批任务信息")
@Data
public static class ApprovalTaskInfo {
public static class ActivityNodeTask {
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String id;
@Schema(description = "任务所属人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1818")
@JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser
private Long owner;
@Schema(description = "任务所属人", example = "1024")
private User ownerUser;
private UserSimpleBaseVO ownerUser;
@Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
@JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
private Long assignee;
@Schema(description = "任务分配人", example = "2048")
private User assigneeUser;
private UserSimpleBaseVO assigneeUser;
@Schema(description = "任务状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status; // 参见 BpmTaskStatusEnum 枚举

View File

@ -1,38 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.AssertTrue;
@Schema(description = "管理后台 - 表单字段权限 Request VO")
@Data
public class BpmFormFieldsPermissionReqVO {
@Schema(description = "流程定义的编号", example = "1024")
private String processDefinitionId;
@Schema(description = "流程实例的编号", example = "1024")
private String processInstanceId;
@Schema(description = "流程活动编号", example = "StartUserNode")
private String activityId; // 对应 BPMN XML 节点 Id
@Schema(description = "流程任务编号", example = "95f2f08b-621b-11ef-bf39-00ff4722db8b")
private String taskId; // UserTask 对应的Id
@AssertTrue(message = "流程定义的编号和流程实例的编号不能同时为空")
@JsonIgnore
public boolean isValidProcessParam() {
return StrUtil.isNotEmpty(processDefinitionId) || StrUtil.isNotEmpty(processInstanceId);
}
@AssertTrue(message = "流程活动编号和流程任务编号编号不能同时为空")
@JsonIgnore
public boolean isValidActivityParam() {
return StrUtil.isNotEmpty(activityId) || StrUtil.isNotEmpty(taskId);
}
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
import java.util.Set;
@Schema(description = "管理后台 - 流程示例的 BPMN 视图 Response VO")
@Data
public class BpmProcessInstanceBpmnModelViewRespVO {
// ========== 基本信息 ==========
@Schema(description = "流程实例信息", requiredMode = Schema.RequiredMode.REQUIRED)
private BpmProcessInstanceRespVO processInstance;
@Schema(description = "任务列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<BpmTaskRespVO> tasks;
@Schema(description = "BPMN XML", requiredMode = Schema.RequiredMode.REQUIRED)
private String bpmnXml;
@Schema(description = "SIMPLE 模型")
private BpmSimpleModelNodeVO simpleModel;
// ========== 进度信息 ==========
@Schema(description = "进行中的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
private Set<String> unfinishedTaskActivityIds; // 只包括 UserTask
@Schema(description = "已经完成的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
private Set<String> finishedTaskActivityIds; // 包括 UserTask、Gateway 等,不包括 SequenceFlow
@Schema(description = "已经完成的连线节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
private Set<String> finishedSequenceFlowActivityIds; // 只包括 SequenceFlow
@Schema(description = "已经拒绝的活动节点编号集合", requiredMode = Schema.RequiredMode.REQUIRED)
private Set<String> rejectedTaskActivityIds; // 只包括 UserTask
}

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance;
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -44,7 +45,7 @@ public class BpmProcessInstanceRespVO {
/**
*
*/
private User startUser;
private UserSimpleBaseVO startUser;
@Schema(description = "流程定义的编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private String processDefinitionId;
@ -58,22 +59,6 @@ public class BpmProcessInstanceRespVO {
*/
private List<Task> tasks; // 仅在流程实例分页才返回
@Schema(description = "用户信息")
@Data
public static class User {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
private String nickname;
@Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long deptId;
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发部")
private String deptName;
}
@Schema(description = "流程任务")
@Data
public static class Task {

View File

@ -1,10 +1,9 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import java.util.Collection;
import java.util.Map;
@Schema(description = "管理后台 - 通过流程任务的 Request VO")
@ -19,9 +18,6 @@ public class BpmTaskApproveReqVO {
@NotEmpty(message = "审批意见不能为空")
private String reason;
@Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "1,2")
private Collection<Long> copyUserIds;
@Schema(description = "变量实例(动态表单)", requiredMode = Schema.RequiredMode.REQUIRED)
private Map<String, Object> variables;

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import java.util.Collection;
@Schema(description = "管理后台 - 抄送流程任务的 Request VO")
@Data
public class BpmTaskCopyReqVO {
@Schema(description = "任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotEmpty(message = "任务编号不能为空")
private String id;
@Schema(description = "抄送的用户编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2]")
@NotEmpty(message = "抄送用户不能为空")
private Collection<Long> copyUserIds;
@Schema(description = "抄送意见", example = "帮忙看看!")
private String reason;
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@ -33,14 +34,21 @@ public class BpmTaskRespVO {
@Schema(description = "审批理由", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private String reason;
@Schema(description = "任务负责人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
@JsonIgnore // 不返回,只是方便后续读取,赋值给 ownerUser
private Long owner;
/**
*
*/
private BpmProcessInstanceRespVO.User ownerUser;
private UserSimpleBaseVO ownerUser;
@Schema(description = "任务分配人编号", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2048")
@JsonIgnore // 不返回,只是方便后续读取,赋值给 assigneeUser
private Long assignee;
/**
*
*/
private BpmProcessInstanceRespVO.User assigneeUser;
private UserSimpleBaseVO assigneeUser;
@Schema(description = "任务定义的标识", requiredMode = Schema.RequiredMode.REQUIRED, example = "Activity_one")
private String taskDefinitionKey;
@ -55,7 +63,7 @@ public class BpmTaskRespVO {
@Schema(description = "父任务编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private String parentTaskId;
@Schema(description = "子任务列表(由加签生成)", requiredMode = Schema.RequiredMode.REQUIRED, example = "childrenTask")
private List<BpmTaskRespVO> children;
private List<BpmTaskRespVO> children; // 由加签生成,包含多层子任务
@Schema(description = "表单编号", example = "1024")
private Long formId;
@ -67,9 +75,6 @@ public class BpmTaskRespVO {
private List<String> formFields;
@Schema(description = "提交的表单值", requiredMode = Schema.RequiredMode.REQUIRED)
private Map<String, Object> formVariables;
// @芋艿 都改成了 fieldsPermission。 buttonsSetting。和 BpmSimpleModelNodeVO 统一
@Schema(description = "表单字段权限值")
private Map<String, String> fieldsPermission;
@Schema(description = "操作按钮设置值")
private Map<Integer, OperationButtonSetting> buttonsSetting;
@ -92,7 +97,7 @@ public class BpmTaskRespVO {
/**
*
*/
private BpmProcessInstanceRespVO.User startUser;
private UserSimpleBaseVO startUser;
}

View File

@ -1,11 +1,10 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
@Schema(description = "管理后台 - 回退流程任务的 Request VO")
@Schema(description = "管理后台 - 退回流程任务的 Request VO")
@Data
public class BpmTaskReturnReqVO {
@ -13,12 +12,12 @@ public class BpmTaskReturnReqVO {
@NotEmpty(message = "任务编号不能为空")
private String id;
@Schema(description = "退到的任务 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotEmpty(message = "退到的任务 Key 不能为空")
@Schema(description = "退到的任务 Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotEmpty(message = "退到的任务 Key 不能为空")
private String targetTaskDefinitionKey;
@Schema(description = "退意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "我就是想驳回")
@NotEmpty(message = "退意见不能为空")
@Schema(description = "退意见", requiredMode = Schema.RequiredMode.REQUIRED, example = "我就是想驳回")
@NotEmpty(message = "退意见不能为空")
private String reason;
}

View File

@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.bpm.convert.definition;
import cn.hutool.core.util.ArrayUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@ -22,6 +21,7 @@ import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@ -37,25 +37,28 @@ public interface BpmModelConvert {
BpmModelConvert INSTANCE = Mappers.getMapper(BpmModelConvert.class);
default PageResult<BpmModelRespVO> buildModelPage(PageResult<Model> pageResult,
Map<Long, BpmFormDO> formMap,
Map<String, BpmCategoryDO> categoryMap, Map<String, Deployment> deploymentMap,
Map<String, ProcessDefinition> processDefinitionMap,
Map<Long, AdminUserRespDTO> userMap) {
List<BpmModelRespVO> list = convertList(pageResult.getList(), model -> {
default List<BpmModelRespVO> buildModelList(List<Model> list,
Map<Long, BpmFormDO> formMap,
Map<String, BpmCategoryDO> categoryMap,
Map<String, Deployment> deploymentMap,
Map<String, ProcessDefinition> processDefinitionMap,
Map<Long, AdminUserRespDTO> userMap) {
List<BpmModelRespVO> result = convertList(list, model -> {
BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
BpmFormDO form = metaInfo != null ? formMap.get(metaInfo.getFormId()) : null;
BpmCategoryDO category = categoryMap.get(model.getCategory());
Deployment deployment = model.getDeploymentId() != null ? deploymentMap.get(model.getDeploymentId()) : null;
ProcessDefinition processDefinition = model.getDeploymentId() != null ? processDefinitionMap.get(model.getDeploymentId()) : null;
ProcessDefinition processDefinition = model.getDeploymentId() != null ?
processDefinitionMap.get(model.getDeploymentId()) : null;
List<AdminUserRespDTO> startUsers = metaInfo != null ? convertList(metaInfo.getStartUserIds(), userMap::get) : null;
return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers);
});
return new PageResult<>(list, pageResult.getTotal());
// 排序
result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort));
return result;
}
default BpmModelRespVO buildModel(Model model,
byte[] bpmnBytes) {
default BpmModelRespVO buildModel(Model model, byte[] bpmnBytes) {
BpmModelMetaInfoVO metaInfo = parseMetaInfo(model);
BpmModelRespVO modelVO = buildModel0(model, metaInfo, null, null, null, null, null);
if (ArrayUtil.isNotEmpty(bpmnBytes)) {
@ -112,6 +115,10 @@ public interface BpmModelConvert {
if (vo.getStartUserIds() == null) {
vo.setStartUserIds(Collections.emptyList());
}
// 如果为空,兜底处理,使用 createTime 创建时间
if (vo.getSort() == null) {
vo.setSort(model.getCreateTime().getTime());
}
return vo;
}

View File

@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.UserTask;
import org.flowable.common.engine.impl.db.SuspensionState;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
@ -20,6 +19,7 @@ import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
@ -47,7 +47,7 @@ public interface BpmProcessDefinitionConvert {
Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap,
Map<Long, BpmFormDO> formMap,
Map<String, BpmCategoryDO> categoryMap) {
return CollectionUtils.convertList(list, definition -> {
List<BpmProcessDefinitionRespVO> result = CollectionUtils.convertList(list, definition -> {
Deployment deployment = MapUtil.get(deploymentMap, definition.getDeploymentId(), Deployment.class);
BpmProcessDefinitionInfoDO processDefinitionInfo = MapUtil.get(processDefinitionInfoMap, definition.getId(), BpmProcessDefinitionInfoDO.class);
BpmFormDO form = null;
@ -55,8 +55,11 @@ public interface BpmProcessDefinitionConvert {
form = MapUtil.get(formMap, processDefinitionInfo.getFormId(), BpmFormDO.class);
}
BpmCategoryDO category = MapUtil.get(categoryMap, definition.getCategory(), BpmCategoryDO.class);
return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null, null);
return buildProcessDefinition(definition, deployment, processDefinitionInfo, form, category, null);
});
// 排序
result.sort(Comparator.comparing(BpmProcessDefinitionRespVO::getSort));
return result;
}
default BpmProcessDefinitionRespVO buildProcessDefinition(ProcessDefinition definition,
@ -64,8 +67,7 @@ public interface BpmProcessDefinitionConvert {
BpmProcessDefinitionInfoDO processDefinitionInfo,
BpmFormDO form,
BpmCategoryDO category,
BpmnModel bpmnModel,
List<UserTask> startUserSelectUserTaskList) {
BpmnModel bpmnModel) {
BpmProcessDefinitionRespVO respVO = BeanUtils.toBean(definition, BpmProcessDefinitionRespVO.class);
respVO.setSuspensionState(definition.isSuspended() ? SuspensionState.SUSPENDED.getStateCode() : SuspensionState.ACTIVE.getStateCode());
// Deployment
@ -87,7 +89,6 @@ public interface BpmProcessDefinitionConvert {
// BpmnModel
if (bpmnModel != null) {
respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
respVO.setStartUserSelectTasks(BeanUtils.toBean(startUserSelectUserTaskList, BpmProcessDefinitionRespVO.UserTask.class));
}
return respVO;
}

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.bpm.convert.task;
import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.activity.BpmActivityRespVO;
import org.flowable.engine.history.HistoricActivityInstance;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.util.List;
/**
* BPM Convert
*
* @author
*/
@Mapper(uses = DateUtils.class)
public interface BpmActivityConvert {
BpmActivityConvert INSTANCE = Mappers.getMapper(BpmActivityConvert.class);
List<BpmActivityRespVO> convertList(List<HistoricActivityInstance> list);
@Mappings({
@Mapping(source = "activityId", target = "key"),
@Mapping(source = "activityType", target = "type")
})
BpmActivityRespVO convert(HistoricActivityInstance bean);
}

View File

@ -1,30 +1,47 @@
package cn.iocoder.yudao.module.bpm.convert.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceBpmnModelViewRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import cn.iocoder.yudao.module.bpm.convert.definition.BpmProcessDefinitionConvert;
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.event.BpmProcessInstanceStatusEvent;
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.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingTarget;
import org.mapstruct.factory.Mappers;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
/**
* Convert
@ -55,7 +72,7 @@ public interface BpmProcessInstanceConvert {
if (userMap != null) {
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(pageResult.getList().get(i).getStartUserId()));
if (startUser != null) {
respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
MapUtils.findAndThen(deptMap, startUser.getDeptId(), dept -> respVO.getStartUser().setDeptName(dept.getName()));
}
}
@ -65,20 +82,18 @@ public interface BpmProcessInstanceConvert {
default BpmProcessInstanceRespVO buildProcessInstance(HistoricProcessInstance processInstance,
ProcessDefinition processDefinition,
BpmProcessDefinitionInfoDO processDefinitionExt,
String bpmnXml,
BpmProcessDefinitionInfoDO processDefinitionInfo,
AdminUserRespDTO startUser,
DeptRespDTO dept) {
BpmProcessInstanceRespVO respVO = BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class);
respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance));
respVO.setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance));
respVO.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance))
.setFormVariables(FlowableUtils.getProcessInstanceFormVariable(processInstance));
// definition
respVO.setProcessDefinition(BeanUtils.toBean(processDefinition, BpmProcessDefinitionRespVO.class));
copyTo(processDefinitionExt, respVO.getProcessDefinition());
respVO.getProcessDefinition().setBpmnXml(bpmnXml);
copyTo(processDefinitionInfo, respVO.getProcessDefinition());
// user
if (startUser != null) {
respVO.setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
respVO.setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
if (dept != null) {
respVO.getStartUser().setDeptName(dept.getName());
}
@ -89,7 +104,7 @@ public interface BpmProcessInstanceConvert {
@Mapping(source = "from.id", target = "to.id", ignore = true)
void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to);
default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {;
default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) {
return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status)
.setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey());
}
@ -109,4 +124,156 @@ public interface BpmProcessInstanceConvert {
.setStartUserId(NumberUtils.parseLong(instance.getStartUserId()));
}
default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance,
List<HistoricTaskInstance> taskInstances,
BpmnModel bpmnModel,
BpmSimpleModelNodeVO simpleModel,
Set<String> unfinishedTaskActivityIds,
Set<String> finishedTaskActivityIds,
Set<String> finishedSequenceFlowActivityIds,
Set<String> rejectTaskActivityIds,
Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap) {
BpmProcessInstanceBpmnModelViewRespVO respVO = new BpmProcessInstanceBpmnModelViewRespVO();
// 基本信息
respVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class, o -> o
.setStatus(FlowableUtils.getProcessInstanceStatus(processInstance)))
.setStartUser(buildUser(processInstance.getStartUserId(), userMap, deptMap)));
respVO.setTasks(convertList(taskInstances, task -> BeanUtils.toBean(task, BpmTaskRespVO.class)
.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task))
.setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap))
.setOwnerUser(buildUser(task.getOwner(), userMap, deptMap))));
respVO.setBpmnXml(BpmnModelUtils.getBpmnXml(bpmnModel));
respVO.setSimpleModel(simpleModel);
// 进度信息
respVO.setUnfinishedTaskActivityIds(unfinishedTaskActivityIds)
.setFinishedTaskActivityIds(finishedTaskActivityIds)
.setFinishedSequenceFlowActivityIds(finishedSequenceFlowActivityIds)
.setRejectedTaskActivityIds(rejectTaskActivityIds);
return respVO;
}
default UserSimpleBaseVO buildUser(String userIdStr,
Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap) {
if (StrUtil.isEmpty(userIdStr)) {
return null;
}
Long userId = NumberUtils.parseLong(userIdStr);
return buildUser(userId, userMap, deptMap);
}
default UserSimpleBaseVO buildUser(Long userId,
Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap) {
if (userId == null) {
return null;
}
AdminUserRespDTO user = userMap.get(userId);
if (user == null) {
return null;
}
UserSimpleBaseVO userVO = BeanUtils.toBean(user, UserSimpleBaseVO.class);
DeptRespDTO dept = user.getDeptId() != null ? deptMap.get(user.getDeptId()) : null;
if (dept != null) {
userVO.setDeptName(dept.getName());
}
return userVO;
}
default BpmApprovalDetailRespVO.ActivityNodeTask buildApprovalTaskInfo(HistoricTaskInstance task) {
if (task == null) {
return null;
}
return BeanUtils.toBean(task, BpmApprovalDetailRespVO.ActivityNodeTask.class)
.setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task));
}
default Set<Long> parseUserIds(HistoricProcessInstance processInstance,
List<BpmApprovalDetailRespVO.ActivityNode> activityNodes,
BpmTaskRespVO todoTask) {
Set<Long> userIds = new HashSet<>();
if (processInstance != null) {
userIds.add(NumberUtils.parseLong(processInstance.getStartUserId()));
}
for (BpmApprovalDetailRespVO.ActivityNode activityNode : activityNodes) {
CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getAssignee));
CollUtil.addAll(userIds, convertSet(activityNode.getTasks(), BpmApprovalDetailRespVO.ActivityNodeTask::getOwner));
CollUtil.addAll(userIds, activityNode.getCandidateUserIds());
}
if (todoTask != null) {
CollUtil.addIfAbsent(userIds, todoTask.getAssignee());
CollUtil.addIfAbsent(userIds, todoTask.getOwner());
if (CollUtil.isNotEmpty(todoTask.getChildren())) {
CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getAssignee));
CollUtil.addAll(userIds, convertSet(todoTask.getChildren(), BpmTaskRespVO::getOwner));
}
}
return userIds;
}
default Set<Long> parseUserIds02(HistoricProcessInstance processInstance,
List<HistoricTaskInstance> tasks) {
Set<Long> userIds = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
tasks.forEach(task -> {
CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getAssignee())));
CollUtil.addIfAbsent(userIds, NumberUtils.parseLong((task.getOwner())));
});
return userIds;
}
default BpmApprovalDetailRespVO buildApprovalDetail(BpmnModel bpmnModel,
ProcessDefinition processDefinition,
BpmProcessDefinitionInfoDO processDefinitionInfo,
HistoricProcessInstance processInstance,
Integer processInstanceStatus,
List<BpmApprovalDetailRespVO.ActivityNode> activityNodes,
BpmTaskRespVO todoTask,
Map<String, String> formFieldsPermission,
Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap) {
// 1.1 流程实例
BpmProcessInstanceRespVO processInstanceResp = null;
if (processInstance != null) {
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
DeptRespDTO dept = startUser != null ? deptMap.get(startUser.getDeptId()) : null;
processInstanceResp = buildProcessInstance(processInstance, null, null, startUser, dept);
}
// 1.2 流程定义
BpmProcessDefinitionRespVO definitionResp = BpmProcessDefinitionConvert.INSTANCE.buildProcessDefinition(
processDefinition, null, processDefinitionInfo, null, null, bpmnModel);
// 1.3 流程节点
activityNodes.forEach(approveNode -> {
if (approveNode.getTasks() != null) {
approveNode.getTasks().forEach(task -> {
task.setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap));
task.setOwnerUser(buildUser(task.getOwner(), userMap, deptMap));
});
}
approveNode.setCandidateUsers(convertList(approveNode.getCandidateUserIds(), userId -> buildUser(userId, userMap, deptMap)));
});
// 1.4 待办任务
if (todoTask != null) {
todoTask.setAssigneeUser(buildUser(todoTask.getAssignee(), userMap, deptMap));
todoTask.setOwnerUser(buildUser(todoTask.getOwner(), userMap, deptMap));
if (CollUtil.isNotEmpty(todoTask.getChildren())) {
todoTask.getChildren().forEach(childTask -> {
childTask.setAssigneeUser(buildUser(childTask.getAssignee(), userMap, deptMap));
childTask.setOwnerUser(buildUser(childTask.getOwner(), userMap, deptMap));
});
}
}
// 2. 拼接起来
return new BpmApprovalDetailRespVO().setStatus(processInstanceStatus)
.setProcessDefinition(definitionResp)
.setProcessInstance(processInstanceResp)
.setFormFieldsPermission(formFieldsPermission)
.setTodoTask(todoTask)
.setActivityNodes(activityNodes);
}
}

View File

@ -1,21 +1,19 @@
package cn.iocoder.yudao.module.bpm.convert.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.base.user.UserSimpleBaseVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
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.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
@ -28,7 +26,7 @@ import java.util.Date;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
/**
@ -51,7 +49,7 @@ public interface BpmTaskConvert {
}
taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
});
}
@ -65,7 +63,7 @@ public interface BpmTaskConvert {
// 用户信息
AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
if (assignUser != null) {
taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class));
taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName()));
}
// 流程实例
@ -73,7 +71,7 @@ public interface BpmTaskConvert {
if (processInstance != null) {
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, UserSimpleBaseVO.class));
}
return taskVO;
});
@ -81,19 +79,17 @@ public interface BpmTaskConvert {
}
default List<BpmTaskRespVO> buildTaskListByProcessInstanceId(List<HistoricTaskInstance> taskList,
HistoricProcessInstance processInstance,
Map<Long, BpmFormDO> formMap,
Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap,
BpmnModel bpmnModel) {
List<BpmTaskRespVO> taskVOList = CollectionUtils.convertList(taskList, task -> {
Map<Long, DeptRespDTO> deptMap) {
return CollectionUtils.convertList(taskList, task -> {
// 特殊:已取消的任务,不返回
BpmTaskRespVO taskVO = BeanUtils.toBean(task, BpmTaskRespVO.class);
Integer taskStatus = FlowableUtils.getTaskStatus(task);
if (BpmTaskStatusEnum.isCancelStatus(taskStatus)) {
return null;
}
taskVO.setStatus(taskStatus).setReason(FlowableUtils.getTaskReason(task));
// 流程实例
AdminUserRespDTO startUser = userMap.get(NumberUtils.parseLong(processInstance.getStartUserId()));
taskVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmTaskRespVO.ProcessInstance.class));
taskVO.getProcessInstance().setStartUser(BeanUtils.toBean(startUser, BpmProcessInstanceRespVO.User.class));
// 表单信息
BpmFormDO form = MapUtil.get(formMap, NumberUtils.parseLong(task.getFormKey()), BpmFormDO.class);
if (form != null) {
@ -101,35 +97,10 @@ public interface BpmTaskConvert {
.setFormFields(form.getFields()).setFormVariables(FlowableUtils.getTaskFormVariable(task));
}
// 用户信息
AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
if (assignUser != null) {
taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class));
findAndThen(deptMap, assignUser.getDeptId(), dept -> taskVO.getAssigneeUser().setDeptName(dept.getName()));
}
AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner()));
if (ownerUser != null) {
taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class));
findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName()));
}
if (BpmTaskStatusEnum.RUNNING.getStatus().equals(taskStatus)){
// 设置表单权限 TODO @芋艿 是不是还要加一个全局的权限 基于 processInstance 的权限;回复:可能不需要,但是发起人,需要有个权限配置
// TODO @jason貌似这么返回主要解决当前审批 task 的表单权限,但是不同抄送人的表单权限,可能不太对。例如说,对 A 抄送人是隐藏某个字段。
// @芋艿 表单权限需要分离开。单独的接口来获取了 BpmProcessInstanceService.getProcessInstanceFormFieldsPermission
taskVO.setFieldsPermission(BpmnModelUtils.parseFormFieldsPermission(bpmnModel, task.getTaskDefinitionKey()));
// 操作按钮设置
taskVO.setButtonsSetting(BpmnModelUtils.parseButtonsSetting(bpmnModel, task.getTaskDefinitionKey()));
}
return taskVO;
buildTaskAssignee(taskVO, task.getAssignee(), userMap, deptMap);
buildTaskOwner(taskVO, task.getOwner(), userMap, deptMap);
return taskVO;
});
// 拼接父子关系
Map<String, List<BpmTaskRespVO>> childrenTaskMap = convertMultiMap(
filterList(taskVOList, r -> StrUtil.isNotEmpty(r.getParentTaskId())),
BpmTaskRespVO::getParentTaskId);
for (BpmTaskRespVO taskVO : taskVOList) {
taskVO.setChildren(childrenTaskMap.get(taskVO.getId()));
}
return filterList(taskVOList, r -> StrUtil.isEmpty(r.getParentTaskId()));
}
default List<BpmTaskRespVO> buildTaskListByParentTaskId(List<Task> taskList,
@ -138,7 +109,7 @@ public interface BpmTaskConvert {
return convertList(taskList, task -> BeanUtils.toBean(task, BpmTaskRespVO.class, taskVO -> {
AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(task.getAssignee()));
if (assignUser != null) {
taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, BpmProcessInstanceRespVO.User.class));
taskVO.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
DeptRespDTO dept = deptMap.get(assignUser.getDeptId());
if (dept != null) {
taskVO.getAssigneeUser().setDeptName(dept.getName());
@ -146,12 +117,21 @@ public interface BpmTaskConvert {
}
AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(task.getOwner()));
if (ownerUser != null) {
taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, BpmProcessInstanceRespVO.User.class));
taskVO.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class));
findAndThen(deptMap, ownerUser.getDeptId(), dept -> taskVO.getOwnerUser().setDeptName(dept.getName()));
}
}));
}
default BpmTaskRespVO buildTodoTask(Task todoTask, List<Task> childrenTasks,
Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting) {
return BeanUtils.toBean(todoTask, BpmTaskRespVO.class)
.setStatus(FlowableUtils.getTaskStatus(todoTask)).setReason(FlowableUtils.getTaskReason(todoTask))
.setButtonsSetting(buttonsSetting)
.setChildren(convertList(childrenTasks, childTask -> BeanUtils.toBean(childTask, BpmTaskRespVO.class)
.setStatus(FlowableUtils.getTaskStatus(childTask))));
}
default BpmMessageSendWhenTaskCreatedReqDTO convert(ProcessInstance processInstance, AdminUserRespDTO startUser,
Task task) {
BpmMessageSendWhenTaskCreatedReqDTO reqDTO = new BpmMessageSendWhenTaskCreatedReqDTO();
@ -162,6 +142,42 @@ public interface BpmTaskConvert {
return reqDTO;
}
default void buildTaskOwner(BpmTaskRespVO task, String taskOwner,
Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap) {
AdminUserRespDTO ownerUser = userMap.get(NumberUtils.parseLong(taskOwner));
if (ownerUser != null) {
task.setOwnerUser(BeanUtils.toBean(ownerUser, UserSimpleBaseVO.class));
findAndThen(deptMap, ownerUser.getDeptId(), dept -> task.getOwnerUser().setDeptName(dept.getName()));
}
}
default void buildTaskChildren(BpmTaskRespVO task, Map<String, List<Task>> childrenTaskMap,
Map<Long, AdminUserRespDTO> userMap, Map<Long, DeptRespDTO> deptMap) {
List<Task> childTasks = childrenTaskMap.get(task.getId());
if (CollUtil.isNotEmpty(childTasks)) {
task.setChildren(
convertList(childTasks, childTask -> {
BpmTaskRespVO childTaskVO = BeanUtils.toBean(childTask, BpmTaskRespVO.class);
childTaskVO.setStatus(FlowableUtils.getTaskStatus(childTask));
buildTaskOwner(childTaskVO, childTask.getOwner(), userMap, deptMap);
buildTaskAssignee(childTaskVO, childTask.getAssignee(), userMap, deptMap);
return childTaskVO;
})
);
}
}
default void buildTaskAssignee(BpmTaskRespVO task, String taskAssignee,
Map<Long, AdminUserRespDTO> userMap,
Map<Long, DeptRespDTO> deptMap) {
AdminUserRespDTO assignUser = userMap.get(NumberUtils.parseLong(taskAssignee));
if (assignUser != null) {
task.setAssigneeUser(BeanUtils.toBean(assignUser, UserSimpleBaseVO.class));
findAndThen(deptMap, assignUser.getDeptId(), dept -> task.getAssigneeUser().setDeptName(dept.getName()));
}
}
/**
*
* <p>

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler;
import cn.iocoder.yudao.framework.mybatis.core.type.StringListTypeHandler;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelTypeEnum;
@ -122,6 +123,10 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
* false
*/
private Boolean visible;
/**
*
*/
private Long sort;
/**
*
@ -134,7 +139,7 @@ public class BpmProcessDefinitionInfoDO extends BaseDO {
* 1. {@link #visible} 使
* 2. startUserIds
*/
@TableField(typeHandler = StringListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
@TableField(typeHandler = LongListTypeHandler.class) // 为了可以使用 find_in_set 进行过滤
private List<Long> startUserIds;
/**

View File

@ -8,6 +8,8 @@ import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.flowable.bpmn.model.FlowNode;
import org.flowable.task.api.history.HistoricTaskInstance;
/**
* DO
@ -54,23 +56,25 @@ public class BpmProcessInstanceCopyDO extends BaseDO {
*/
private String category;
/**
*
*
* <p/>
* BPMN XML
* (ServiceTask)使 taskId activityId
*
* {@link FlowNode#getId()} BPMN XML
* 仿/ (ServiceTask) taskId activityId
*/
private String activityId;
/**
*
* Task id
*
*
* {@link FlowNode#getName()}
*/
private String activityName;
/**
*
*
* {@link HistoricTaskInstance#getId()}
*/
private String taskId;
/**
*
*
* Task name
*/
private String taskName;
/**
*
@ -79,4 +83,9 @@ public class BpmProcessInstanceCopyDO extends BaseDO {
*/
private Long userId;
/**
*
*/
private String reason;
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.dal.mysql.definition;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import org.apache.ibatis.annotations.Mapper;
@ -18,4 +19,9 @@ public interface BpmProcessDefinitionInfoMapper extends BaseMapperX<BpmProcessDe
return selectOne(BpmProcessDefinitionInfoDO::getProcessDefinitionId, processDefinitionId);
}
default void updateByModelId(String modelId, BpmProcessDefinitionInfoDO updateObj) {
update(updateObj,
new LambdaQueryWrapperX<BpmProcessDefinitionInfoDO>().eq(BpmProcessDefinitionInfoDO::getModelId, modelId));
}
}

View File

@ -53,7 +53,7 @@ public class BpmParallelMultiInstanceBehavior extends ParallelMultiInstanceBehav
@SuppressWarnings("unchecked")
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
if (assigneeUserIds == null) {
assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
execution.setVariable(super.collectionVariable, assigneeUserIds);
if (CollUtil.isEmpty(assigneeUserIds)) {
// 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!

View File

@ -46,7 +46,7 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
@SuppressWarnings("unchecked")
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariable(super.collectionVariable, Set.class);
if (assigneeUserIds == null) {
assigneeUserIds = taskCandidateInvoker.calculateUsers(execution);
assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
execution.setVariable(super.collectionVariable, assigneeUserIds);
if (CollUtil.isEmpty(assigneeUserIds)) {
// 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!

View File

@ -57,7 +57,7 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
// 情况二,如果非多实例的任务,则计算任务处理人
// 第一步,先计算可处理该任务的处理人们
Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsers(execution);
Set<Long> candidateUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
if (CollUtil.isEmpty(candidateUserIds)) {
return null;
}

View File

@ -18,6 +18,7 @@ import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
@ -89,32 +90,66 @@ public class BpmTaskCandidateInvoker {
* @return
*/
@DataPermission(enable = false) // 忽略数据权限,避免因为过滤,导致找不到候选人
public Set<Long> calculateUsers(DelegateExecution execution) {
public Set<Long> calculateUsersByTask(DelegateExecution execution) {
// 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
Integer approveType = BpmnModelUtils.parseApproveType(execution.getCurrentFlowElement());
FlowElement flowElement = execution.getCurrentFlowElement();
Integer approveType = BpmnModelUtils.parseApproveType(flowElement);
if (ObjectUtils.equalsAny(approveType,
BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
return new HashSet<>();
}
Integer strategy = BpmnModelUtils.parseCandidateStrategy(execution.getCurrentFlowElement());
String param = BpmnModelUtils.parseCandidateParam(execution.getCurrentFlowElement());
// 1.1 计算任务的候选人
Set<Long> userIds = getCandidateStrategy(strategy).calculateUsers(execution, param);
removeDisableUsers(userIds);
Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement);
String param = BpmnModelUtils.parseCandidateParam(flowElement);
Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByTask(execution, param);
// 1.2 移除被禁用的用户
removeDisableUsers(userIds);
// 2. 候选人为空时,根据“审批人为空”的配置补充
if (CollUtil.isEmpty(userIds)) {
userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy())
.calculateUsers(execution, param);
.calculateUsersByTask(execution, param);
// ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!!
}
// 3. 移除发起人的用户
removeStartUserIfSkip(execution, userIds);
ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class)
.getProcessInstance(execution.getProcessInstanceId());
Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId());
removeStartUserIfSkip(userIds, flowElement, Long.valueOf(processInstance.getStartUserId()));
return userIds;
}
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
// 审批类型非人工审核时,不进行计算候选人。原因是:后续会自动通过、不通过
FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
Integer approveType = BpmnModelUtils.parseApproveType(flowElement);
if (ObjectUtils.equalsAny(approveType,
BpmUserTaskApproveTypeEnum.AUTO_APPROVE.getType(),
BpmUserTaskApproveTypeEnum.AUTO_REJECT.getType())) {
return new HashSet<>();
}
// 1.1 计算任务的候选人
Integer strategy = BpmnModelUtils.parseCandidateStrategy(flowElement);
String param = BpmnModelUtils.parseCandidateParam(flowElement);
Set<Long> userIds = getCandidateStrategy(strategy).calculateUsersByActivity(bpmnModel, activityId, param,
startUserId, processDefinitionId, processVariables);
// 1.2 移除被禁用的用户
removeDisableUsers(userIds);
// 2. 候选人为空时,根据“审批人为空”的配置补充
if (CollUtil.isEmpty(userIds)) {
userIds = getCandidateStrategy(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY.getStrategy())
.calculateUsersByActivity(bpmnModel, activityId, param, startUserId, processDefinitionId, processVariables);
// ASSIGN_EMPTY 策略,不需要移除被禁用的用户。原因是,再移除,可能会出现更没审批人了!!!
}
// 3. 移除发起人的用户
removeStartUserIfSkip(userIds, flowElement, startUserId);
return userIds;
}
@ -126,7 +161,7 @@ public class BpmTaskCandidateInvoker {
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(assigneeUserIds);
assigneeUserIds.removeIf(id -> {
AdminUserRespDTO user = userMap.get(id);
return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
return user == null || CommonStatusEnum.isDisable(user.getStatus());
});
}
@ -135,25 +170,23 @@ public class BpmTaskCandidateInvoker {
*
*
*
* @param execution
* @param assigneeUserIds
* @param flowElement
* @param startUserId
*/
@VisibleForTesting
void removeStartUserIfSkip(DelegateExecution execution, Set<Long> assigneeUserIds) {
void removeStartUserIfSkip(Set<Long> assigneeUserIds, FlowElement flowElement, Long startUserId) {
if (CollUtil.size(assigneeUserIds) <= 1) {
return;
}
Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(execution.getCurrentFlowElement());
Integer assignStartUserHandlerType = BpmnModelUtils.parseAssignStartUserHandlerType(flowElement);
if (ObjectUtil.notEqual(assignStartUserHandlerType, BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType())) {
return;
}
ProcessInstance processInstance = SpringUtil.getBean(BpmProcessInstanceService.class)
.getProcessInstance(execution.getProcessInstanceId());
Assert.notNull(processInstance, "流程实例({}) 不存在", execution.getProcessInstanceId());
assigneeUserIds.remove(Long.valueOf(processInstance.getStartUserId()));
assigneeUserIds.remove(startUserId);
}
public BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
private BpmTaskCandidateStrategy getCandidateStrategy(Integer strategy) {
BpmTaskCandidateStrategyEnum strategyEnum = BpmTaskCandidateStrategyEnum.valueOf(strategy);
Assert.notNull(strategyEnum, "策略(%s) 不存在", strategy);
BpmTaskCandidateStrategy strategyObj = strategyMap.get(strategyEnum);

View File

@ -1,10 +1,10 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
/**
@ -42,47 +42,44 @@ public interface BpmTaskCandidateStrategy {
/**
*
*
* calculateUsers
* 1. calculateUsers
* 2. calculateUsersByTask calculateUsersByActivity
*
* @param param
* @return
*/
default Set<Long> calculateUsers(String param) {
return Collections.emptySet();
throw new UnsupportedOperationException("该分配方法未实现,请检查!");
}
/**
*
*
*
* @param execution
* @return
*/
default Set<Long> calculateUsers(DelegateExecution execution, String param) {
Set<Long> users = calculateUsers(param);
removeDisableUsers(users);
return users;
default Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
return calculateUsers(param);
}
/**
*
*
* <p>
*
*
* @param startUserId
* @param processInstance
* @param activityId Id ( Bpmn XML id)
* @param bpmnModel
* @param activityId ID ( Bpmn XML id)
* @param param
* @param startUserId
* @param processDefinitionId
* @param processVariables
* @return
*/
default Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
Set<Long> users = calculateUsers(param);
removeDisableUsers(users);
return users;
@SuppressWarnings("unused")
default Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
return calculateUsers(param);
}
/**
*
*
* @param users Ids
*/
void removeDisableUsers(Set<Long> users);
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import java.util.Map;
import java.util.Set;
/**
* {@link BpmTaskCandidateStrategy}
*
* @author jason
*/
public abstract class BpmTaskCandidateAbstractStrategy implements BpmTaskCandidateStrategy {
protected AdminUserApi adminUserApi;
public BpmTaskCandidateAbstractStrategy(AdminUserApi adminUserApi) {
this.adminUserApi = adminUserApi;
}
@Override
public void removeDisableUsers(Set<Long> users) {
if (CollUtil.isEmpty(users)) {
return;
}
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(users);
users.removeIf(id -> {
AdminUserRespDTO user = userMap.get(id);
return user == null || !CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus());
});
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
@ -6,6 +6,8 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import java.util.HashSet;
import java.util.LinkedHashSet;
@ -17,14 +19,12 @@ import java.util.Set;
*
* @author jason
*/
public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy {
public abstract class AbstractBpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy {
@Resource
protected DeptApi deptApi;
public BpmTaskCandidateAbstractDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
super(adminUserApi);
this.deptApi = deptApi;
}
@Resource
protected AdminUserApi adminUserApi;
/**
* level
@ -78,4 +78,17 @@ public abstract class BpmTaskCandidateAbstractDeptLeaderStrategy extends BpmTas
return deptLeaderIds;
}
/**
*
*
* @param startUserId Id
*/
protected DeptRespDTO getStartUserDept(Long startUserId) {
AdminUserRespDTO startUser = adminUserApi.getUser(startUserId).getCheckedData();
if (startUser.getDeptId() == null) { // 找不到部门
return null;
}
return deptApi.getDept(startUser.getDeptId()).getCheckedData();
}
}

View File

@ -1,13 +1,12 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set;
/**
@ -16,11 +15,7 @@ import java.util.Set;
* @author jason
*/
@Component
public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
public BpmTaskCandidateDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
super(adminUserApi, deptApi);
}
public class BpmTaskCandidateDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@ -32,14 +27,19 @@ public class BpmTaskCandidateDeptLeaderMultiStrategy extends BpmTaskCandidateAbs
// 参数格式: | 分隔1左边为部门多个部门用 , 分隔。2右边为部门层级
String[] params = param.split("\\|");
Assert.isTrue(params.length == 2, "参数格式不匹配");
deptApi.validateDeptList(StrUtils.splitToLong(params[0], ",")).checkError();
Assert.isTrue(Integer.parseInt(params[1]) > 0, "部门层级必须大于 0");
List<Long> deptIds = StrUtils.splitToLong(params[0], ",");
int level = Integer.parseInt(params[1]);
// 校验部门存在
deptApi.validateDeptList(deptIds).checkError();
Assert.isTrue(level > 0, "部门层级必须大于 0");
}
@Override
public Set<Long> calculateUsers(String param) {
String[] params = param.split("\\|");
return getMultiLevelDeptLeaderIds(StrUtils.splitToLong(params[0], ","), Integer.valueOf(params[1]));
List<Long> deptIds = StrUtils.splitToLong(params[0], ",");
int level = Integer.parseInt(params[1]);
return super.getMultiLevelDeptLeaderIds(deptIds, level);
}
}

View File

@ -1,11 +1,11 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.List;
@ -19,14 +19,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
* @author kyle
*/
@Component
public class BpmTaskCandidateDeptLeaderStrategy extends BpmTaskCandidateAbstractStrategy {
public class BpmTaskCandidateDeptLeaderStrategy implements BpmTaskCandidateStrategy {
private final DeptApi deptApi;
public BpmTaskCandidateDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
super(adminUserApi);
this.deptApi = deptApi;
}
@Resource
private DeptApi deptApi;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@ -36,7 +32,7 @@ public class BpmTaskCandidateDeptLeaderStrategy extends BpmTaskCandidateAbstract
@Override
public void validateParam(String param) {
Set<Long> deptIds = StrUtils.splitToLongSet(param);
deptApi.validateDeptList(deptIds);
deptApi.validateDeptList(deptIds).checkError();
}
@Override

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.List;
@ -19,14 +20,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
* @author kyle
*/
@Component
public class BpmTaskCandidateDeptMemberStrategy extends BpmTaskCandidateAbstractStrategy {
public class BpmTaskCandidateDeptMemberStrategy implements BpmTaskCandidateStrategy {
private final DeptApi deptApi;
public BpmTaskCandidateDeptMemberStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
super(adminUserApi);
this.deptApi = deptApi;
}
@Resource
private DeptApi deptApi;
@Resource
private AdminUserApi adminUserApi;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@ -36,7 +35,7 @@ public class BpmTaskCandidateDeptMemberStrategy extends BpmTaskCandidateAbstract
@Override
public void validateParam(String param) {
Set<Long> deptIds = StrUtils.splitToLongSet(param);
deptApi.validateDeptList(deptIds);
deptApi.validateDeptList(deptIds).checkError();
}
@Override

View File

@ -1,21 +1,20 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static cn.hutool.core.collection.ListUtil.toList;
@ -26,16 +25,12 @@ import static cn.hutool.core.collection.ListUtil.toList;
* @author jason
*/
@Component
public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
@Resource
@Lazy
private BpmProcessInstanceService processInstanceService;
public BpmTaskCandidateStartUserDeptLeaderMultiStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
super(adminUserApi, deptApi);
}
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER_MULTI;
@ -43,48 +38,33 @@ public class BpmTaskCandidateStartUserDeptLeaderMultiStrategy extends BpmTaskCan
@Override
public void validateParam(String param) {
// 参数是部门的层级
Assert.isTrue(Integer.parseInt(param) > 0, "部门的层级必须大于 0");
int level = Integer.parseInt(param); // 参数是部门的层级
Assert.isTrue(level > 0, "部门的层级必须大于 0");
}
@Override
public Set<Long> calculateUsers(DelegateExecution execution, String param) {
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
int level = Integer.parseInt(param); // 参数是部门的层级
// 获得流程发起人
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
// 获取发起人的 multi 部门负责人
DeptRespDTO dept = getStartUserDept(startUserId);
DeptRespDTO dept = super.getStartUserDept(startUserId);
if (dept == null) {
return new HashSet<>();
}
Set<Long> users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级
// TODO @jason这里 removeDisableUsers 的原因是啥呀?
removeDisableUsers(users);
return users;
return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level);
}
@Override
public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
DeptRespDTO dept = getStartUserDept(startUserId);
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
int level = Integer.parseInt(param); // 参数是部门的层级
DeptRespDTO dept = super.getStartUserDept(startUserId);
if (dept == null) {
return new HashSet<>();
}
Set<Long> users = getMultiLevelDeptLeaderIds(toList(dept.getId()), Integer.valueOf(param)); // 参数是部门的层级
removeDisableUsers(users);
return users;
}
/**
*
*
* @param startUserId Id
*/
protected DeptRespDTO getStartUserDept(Long startUserId) {
AdminUserRespDTO startUser = adminUserApi.getUser(startUserId).getCheckedData();
if (startUser.getDeptId() == null) { // 找不到部门
return null;
}
return deptApi.getDept(startUser.getDeptId()).getCheckedData();
return super.getMultiLevelDeptLeaderIds(toList(dept.getId()), level);
}
}

View File

@ -1,21 +1,20 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
@ -26,7 +25,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
* @author jason
*/
@Component
public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidateAbstractDeptLeaderStrategy {
public class BpmTaskCandidateStartUserDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
@Resource
@Lazy // 避免循环依赖
@ -37,10 +36,6 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat
return BpmTaskCandidateStrategyEnum.START_USER_DEPT_LEADER;
}
public BpmTaskCandidateStartUserDeptLeaderStrategy(AdminUserApi adminUserApi, DeptApi deptApi) {
super(adminUserApi, deptApi);
}
@Override
public void validateParam(String param) {
// 参数是部门的层级
@ -48,44 +43,29 @@ public class BpmTaskCandidateStartUserDeptLeaderStrategy extends BpmTaskCandidat
}
@Override
public Set<Long> calculateUsers(DelegateExecution execution, String param) {
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
// 获得流程发起人
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Long startUserId = NumberUtils.parseLong(processInstance.getStartUserId());
// 获取发起人的部门负责人
Set<Long> users = getStartUserDeptLeader(startUserId, param);
removeDisableUsers(users);
return users;
return getStartUserDeptLeader(startUserId, param);
}
@Override
public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
// 获取发起人的部门负责人
Set<Long> users = getStartUserDeptLeader(startUserId, param);
removeDisableUsers(users);
return users;
return getStartUserDeptLeader(startUserId, param);
}
private Set<Long> getStartUserDeptLeader(Long startUserId, String param) {
DeptRespDTO dept = getStartUserDept(startUserId);
int level = Integer.parseInt(param); // 参数是部门的层级
DeptRespDTO dept = super.getStartUserDept(startUserId);
if (dept == null) {
return new HashSet<>();
}
Long deptLeaderId = getAssignLevelDeptLeaderId(dept, Integer.valueOf(param)); // 参数是部门的层级
Long deptLeaderId = super.getAssignLevelDeptLeaderId(dept, level);
return deptLeaderId != null ? asSet(deptLeaderId) : new HashSet<>();
}
/**
*
*
* @param startUserId Id
*/
protected DeptRespDTO getStartUserDept(Long startUserId) {
AdminUserRespDTO startUser = adminUserApi.getUser(startUserId).getCheckedData();
if (startUser.getDeptId() == null) { // 找不到部门
return null;
}
return deptApi.getDept(startUser.getDeptId()).getCheckedData();
}
}

View File

@ -1,20 +1,24 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
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.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import com.google.common.collect.Sets;
import jakarta.annotation.Resource;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ServiceTask;
import org.flowable.bpmn.model.Task;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.*;
/**
@ -23,16 +27,12 @@ import java.util.*;
* @author
*/
@Component
public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbstractStrategy {
public class BpmTaskCandidateStartUserSelectStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
@Resource
@Lazy // 延迟加载,避免循环依赖
private BpmProcessInstanceService processInstanceService;
public BpmTaskCandidateStartUserSelectStrategy(AdminUserApi adminUserApi) {
super(adminUserApi);
}
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.START_USER_SELECT;
@ -42,7 +42,12 @@ public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbs
public void validateParam(String param) {}
@Override
public Set<Long> calculateUsers(DelegateExecution execution, String param) {
public boolean isParamRequired() {
return false;
}
@Override
public LinkedHashSet<Long> calculateUsersByTask(DelegateExecution execution, String param) {
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Assert.notNull(processInstance, "流程实例({})不能为空", execution.getProcessInstanceId());
Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
@ -50,47 +55,43 @@ public class BpmTaskCandidateStartUserSelectStrategy extends BpmTaskCandidateAbs
execution.getProcessInstanceId());
// 获得审批人
List<Long> assignees = startUserSelectAssignees.get(execution.getCurrentActivityId());
Set<Long> users = new LinkedHashSet<>(assignees);
removeDisableUsers(users);
return users;
return new LinkedHashSet<>(assignees);
}
@Override
public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
if (processInstance == null) {
return Collections.emptySet();
public LinkedHashSet<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
if (processVariables == null) {
return Sets.newLinkedHashSet();
}
Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processVariables);
if (startUserSelectAssignees == null) {
return Sets.newLinkedHashSet();
}
Map<String, List<Long>> startUserSelectAssignees = FlowableUtils.getStartUserSelectAssignees(processInstance);
Assert.notNull(startUserSelectAssignees, "流程实例({}) 的发起人自选审批人不能为空", processInstance.getId());
// 获得审批人
List<Long> assignees = startUserSelectAssignees.get(activityId);
Set<Long> users = new LinkedHashSet<>(assignees);
removeDisableUsers(users);
return users;
}
@Override
public boolean isParamRequired() {
return false;
return new LinkedHashSet<>(assignees);
}
/**
* UserTask
* Task
*
* @param bpmnModel BPMN
* @return UserTask
* @return Task
*/
public static List<UserTask> getStartUserSelectUserTaskList(BpmnModel bpmnModel) {
public static List<Task> getStartUserSelectTaskList(BpmnModel bpmnModel) {
if (bpmnModel == null) {
return null;
return Collections.emptyList();
}
List<UserTask> userTaskList = BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class);
if (CollUtil.isEmpty(userTaskList)) {
return null;
List<Task> tasks = new ArrayList<>();
tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, UserTask.class));
tasks.addAll(BpmnModelUtils.getBpmnModelElements(bpmnModel, ServiceTask.class));
if (CollUtil.isEmpty(tasks)) {
return Collections.emptyList();
}
userTaskList.removeIf(userTask -> !Objects.equals(BpmnModelUtils.parseCandidateStrategy(userTask),
tasks.removeIf(task -> ObjectUtil.notEqual(BpmnModelUtils.parseCandidateStrategy(task),
BpmTaskCandidateStrategyEnum.START_USER_SELECT.getStrategy()));
return userTaskList;
return tasks;
}
}

View File

@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept.AbstractBpmTaskCandidateDeptLeaderStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
/**
* {@link BpmTaskCandidateStrategy}
*
* @author jason
*/
@Component
public class BpmTaskCandidateFormSDeptLeaderStrategy extends AbstractBpmTaskCandidateDeptLeaderStrategy {
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.FORM_DEPT_LEADER;
}
@Override
public void validateParam(String param) {
// 参数格式: | 分隔1左边为表单内部门字段。2右边为部门层级
String[] params = param.split("\\|");
Assert.isTrue(params.length == 2, "参数格式不匹配");
Assert.notEmpty(param, "表单内部门字段不能为空");
int level = Integer.parseInt(params[1]);
Assert.isTrue(level > 0, "部门层级必须大于 0");
}
@Override
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
String[] params = param.split("\\|");
Object result = execution.getVariable(params[0]);
int level = Integer.parseInt(params[1]);
return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level);
}
@Override
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
String param, Long startUserId, String processDefinitionId,
Map<String, Object> processVariables) {
String[] params = param.split("\\|");
Object result = processVariables == null ? null : processVariables.get(params[0]);
int level = Integer.parseInt(params[1]);
return super.getMultiLevelDeptLeaderIds(Convert.toList(Long.class, result), level);
}
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.form;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
/**
* {@link BpmTaskCandidateUserStrategy}
*
* @author jason
*/
@Component
public class BpmTaskCandidateFormUserStrategy implements BpmTaskCandidateStrategy {
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.FORM_USER;
}
@Override
public void validateParam(String param) {
Assert.notEmpty(param, "表单内用户字段不能为空");
}
@Override
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
Object result = execution.getVariable(param);
return Convert.toSet(Long.class, result);
}
@Override
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId,
String param, Long startUserId, String processDefinitionId,
Map<String, Object> processVariables) {
Object result = processVariables == null ? null : processVariables.get(param);
return Convert.toSet(Long.class, result);
}
}

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
import cn.hutool.core.lang.Assert;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
@ -7,13 +7,15 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCand
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
@ -23,16 +25,12 @@ import java.util.Set;
* @author kyle
*/
@Component
public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstractStrategy {
public class BpmTaskCandidateAssignEmptyStrategy implements BpmTaskCandidateStrategy {
@Resource
@Lazy // 延迟加载,避免循环依赖
private BpmProcessDefinitionService processDefinitionService;
public BpmTaskCandidateAssignEmptyStrategy(AdminUserApi adminUserApi) {
super(adminUserApi);
}
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY;
@ -43,19 +41,28 @@ public class BpmTaskCandidateAssignEmptyStrategy extends BpmTaskCandidateAbstrac
}
@Override
public Set<Long> calculateUsers(DelegateExecution execution, String param) {
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
return getCandidateUsers(execution.getProcessDefinitionId(), execution.getCurrentFlowElement());
}
@Override
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
FlowElement flowElement = BpmnModelUtils.getFlowElementById(bpmnModel, activityId);
return getCandidateUsers(processDefinitionId, flowElement);
}
private Set<Long> getCandidateUsers(String processDefinitionId, FlowElement flowElement) {
// 情况一:指定人员审批
Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(execution.getCurrentFlowElement());
Integer assignEmptyHandlerType = BpmnModelUtils.parseAssignEmptyHandlerType(flowElement);
if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_USER.getType())) {
Set<Long> users = new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(execution.getCurrentFlowElement()));
removeDisableUsers(users);
return users;
return new HashSet<>(BpmnModelUtils.parseAssignEmptyHandlerUserIds(flowElement));
}
// 情况二:流程管理员
if (Objects.equals(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.ASSIGN_ADMIN.getType())) {
BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(execution.getProcessDefinitionId());
Assert.notNull(processDefinition, "流程定义({})不存在", execution.getProcessDefinitionId());
BpmProcessDefinitionInfoDO processDefinition = processDefinitionService.getProcessDefinitionInfo(processDefinitionId);
Assert.notNull(processDefinition, "流程定义({})不存在", processDefinitionId);
return new HashSet<>(processDefinition.getManagerUserIds());
}

View File

@ -1,13 +1,14 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other;
import cn.hutool.core.convert.Convert;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Set;
/**
@ -16,11 +17,7 @@ import java.util.Set;
* @author
*/
@Component
public class BpmTaskCandidateExpressionStrategy extends BpmTaskCandidateAbstractStrategy {
public BpmTaskCandidateExpressionStrategy(AdminUserApi adminUserApi) {
super(adminUserApi);
}
public class BpmTaskCandidateExpressionStrategy implements BpmTaskCandidateStrategy {
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@ -33,11 +30,16 @@ public class BpmTaskCandidateExpressionStrategy extends BpmTaskCandidateAbstract
}
@Override
public Set<Long> calculateUsers(DelegateExecution execution, String param) {
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
Object result = FlowableUtils.getExpressionValue(execution, param);
Set<Long> users = Convert.toSet(Long.class, result);
removeDisableUsers(users);
return users;
return Convert.toSet(Long.class, result);
}
@Override
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
Object result = FlowableUtils.getExpressionValue(processVariables, param);
return Convert.toSet(Long.class, result);
}
}

View File

@ -1,11 +1,11 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmUserGroupService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.Collection;
@ -20,14 +20,10 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
* @author kyle
*/
@Component
public class BpmTaskCandidateGroupStrategy extends BpmTaskCandidateAbstractStrategy {
public class BpmTaskCandidateGroupStrategy implements BpmTaskCandidateStrategy {
private final BpmUserGroupService userGroupService;
public BpmTaskCandidateGroupStrategy(AdminUserApi adminUserApi, BpmUserGroupService userGroupService) {
super(adminUserApi);
this.userGroupService = userGroupService;
}
@Resource
private BpmUserGroupService userGroupService;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@ -37,7 +33,7 @@ public class BpmTaskCandidateGroupStrategy extends BpmTaskCandidateAbstractStrat
@Override
public void validateParam(String param) {
Set<Long> groupIds = StrUtils.splitToLongSet(param);
userGroupService.getUserGroupList(groupIds);
userGroupService.validUserGroups(groupIds);
}
@Override

View File

@ -1,4 +1,4 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidat
import cn.iocoder.yudao.module.system.api.dept.PostApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.List;
@ -19,14 +20,12 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
* @author kyle
*/
@Component
public class BpmTaskCandidatePostStrategy extends BpmTaskCandidateAbstractStrategy {
public class BpmTaskCandidatePostStrategy implements BpmTaskCandidateStrategy {
private final PostApi postApi;
public BpmTaskCandidatePostStrategy(AdminUserApi adminUserApi, PostApi postApi) {
super(adminUserApi);
this.postApi = postApi;
}
@Resource
private PostApi postApi;
@Resource
private AdminUserApi adminUserApi;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {

View File

@ -1,14 +1,13 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Set;
/**
@ -17,17 +16,13 @@ import java.util.Set;
* @author kyle
*/
@Component
public class BpmTaskCandidateRoleStrategy extends BpmTaskCandidateAbstractStrategy {
public class BpmTaskCandidateRoleStrategy implements BpmTaskCandidateStrategy {
@Resource
private RoleApi roleApi;
@Resource
private PermissionApi permissionApi;
public BpmTaskCandidateRoleStrategy(AdminUserApi adminUserApi) {
super(adminUserApi);
}
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.ROLE;

View File

@ -1,15 +1,17 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Map;
import java.util.Set;
/**
@ -20,16 +22,12 @@ import java.util.Set;
* @author jason
*/
@Component
public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractStrategy {
public class BpmTaskCandidateStartUserStrategy implements BpmTaskCandidateStrategy {
@Resource
@Lazy // 延迟加载,避免循环依赖
private BpmProcessInstanceService processInstanceService;
public BpmTaskCandidateStartUserStrategy(AdminUserApi adminUserApi) {
super(adminUserApi);
}
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
return BpmTaskCandidateStrategyEnum.START_USER;
@ -45,18 +43,15 @@ public class BpmTaskCandidateStartUserStrategy extends BpmTaskCandidateAbstractS
}
@Override
public Set<Long> calculateUsers(DelegateExecution execution, String param) {
public Set<Long> calculateUsersByTask(DelegateExecution execution, String param) {
ProcessInstance processInstance = processInstanceService.getProcessInstance(execution.getProcessInstanceId());
Set<Long> users = SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
removeDisableUsers(users);
return users;
return SetUtils.asSet(Long.valueOf(processInstance.getStartUserId()));
}
@Override
public Set<Long> calculateUsers(Long startUserId, ProcessInstance processInstance, String activityId, String param) {
Set<Long> users = SetUtils.asSet(startUserId);
removeDisableUsers(users);
return users;
public Set<Long> calculateUsersByActivity(BpmnModel bpmnModel, String activityId, String param,
Long startUserId, String processDefinitionId, Map<String, Object> processVariables) {
return SetUtils.asSet(startUserId);
}
}

View File

@ -1,14 +1,14 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user;
import cn.hutool.core.text.StrPool;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
import java.util.LinkedHashSet;
import java.util.Set;
/**
* {@link BpmTaskCandidateStrategy}
@ -16,11 +16,10 @@ import java.util.Set;
* @author kyle
*/
@Component
public class BpmTaskCandidateUserStrategy extends BpmTaskCandidateAbstractStrategy {
public class BpmTaskCandidateUserStrategy implements BpmTaskCandidateStrategy {
public BpmTaskCandidateUserStrategy(AdminUserApi adminUserApi) {
super(adminUserApi);
}
@Resource
private AdminUserApi adminUserApi;
@Override
public BpmTaskCandidateStrategyEnum getStrategy() {
@ -33,7 +32,7 @@ public class BpmTaskCandidateUserStrategy extends BpmTaskCandidateAbstractStrate
}
@Override
public Set<Long> calculateUsers(String param) {
public LinkedHashSet<Long> calculateUsers(String param) {
return new LinkedHashSet<>(StrUtils.splitToLong(param, StrPool.COMMA));
}

View File

@ -4,9 +4,10 @@ import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.el.function.AbstractFlowableVariableExpressionFunction;
import org.springframework.stereotype.Component;
// TODO @jason这个自定义转换的原因是啥呀
/**
* variable ,
* variable
*
* ConditionNodeConvert buildConditionExpression
*
* @author jason
*/
@ -20,11 +21,12 @@ public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVar
public static Object convertByType(VariableContainer variableContainer, String variableName, Object parmaValue) {
Object variable = variableContainer.getVariable(variableName);
if (variable != null && parmaValue != null) {
// 如果值不是字符串类型, 流程变量的类型是字符串。 把值转成字符串
// 如果值不是字符串类型,流程变量的类型是字符串,把值转成字符串
if (!(parmaValue instanceof String) && variable instanceof String ) {
return parmaValue.toString();
}
}
return parmaValue;
}
}

View File

@ -29,6 +29,8 @@ public enum BpmTaskCandidateStrategyEnum implements IntArrayValuable {
START_USER_DEPT_LEADER(37, "发起人部门负责人"),
START_USER_DEPT_LEADER_MULTI(38, "发起人连续多级部门的负责人"),
USER_GROUP(40, "用户组"),
FORM_USER(50, "表单内用户字段"),
FORM_DEPT_LEADER(51, "表单内部门负责人"),
EXPRESSION(60, "流程表达式"), // 表达式 ExpressionManager
ASSIGN_EMPTY(1, "审批人为空"),
;

View File

@ -52,7 +52,7 @@ public interface BpmnModelConstants {
*/
String USER_TASK_REJECT_HANDLER_TYPE = "rejectHandlerType";
/**
* BPMN ExtensionElement 退 Id
* BPMN ExtensionElement 退 Id
*/
String USER_TASK_REJECT_RETURN_TASK_ID = "rejectReturnTaskId";
@ -104,10 +104,6 @@ public interface BpmnModelConstants {
* BPMN Start Event Node Id
*/
String START_EVENT_NODE_ID = "StartEvent";
/**
* BPMN Start Event Node Name
*/
String START_EVENT_NODE_NAME = "开始";
/**
* ID

View File

@ -29,7 +29,12 @@ public class BpmnVariableConstants {
* @see ProcessInstance#getProcessVariables()
*/
public static final String PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES = "PROCESS_START_USER_SELECT_ASSIGNEES";
/**
* - ID
*
* @see ProcessInstance#getProcessVariables()
*/
public static final String PROCESS_INSTANCE_VARIABLE_START_USER_ID = "PROCESS_START_USER_ID";
/**
* - . RETURN_FLAG_{ id}
*

View File

@ -1,33 +0,0 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums;
// TODO @jason要不合并到 BpmnModelConstants 那
/**
* 仿 JSON
*
* @author jason
*/
public interface SimpleModelConstants {
// TODO @芋艿:条件表达式的字段名
/**
*
*/
String DEFAULT_FLOW_ATTRIBUTE = "defaultFlow";
/**
*
*/
String CONDITION_TYPE_ATTRIBUTE = "conditionType";
/**
*
*/
String CONDITION_EXPRESSION_ATTRIBUTE = "conditionExpression";
/**
*
*/
String CONDITION_GROUPS_ATTRIBUTE = "conditionGroups";
}

View File

@ -15,8 +15,8 @@ import static cn.iocoder.yudao.module.bpm.framework.flowable.core.listener.BpmCo
/**
* {@link JavaDelegate}
*
* 使
* <p>
* 仿/使
*
* @author jason
*/
@ -34,14 +34,14 @@ public class BpmCopyTaskDelegate implements JavaDelegate {
@Override
public void execute(DelegateExecution execution) {
// 1. 获得抄送人
Set<Long> userIds = taskCandidateInvoker.calculateUsers(execution);
Set<Long> userIds = taskCandidateInvoker.calculateUsersByTask(execution);
if (CollUtil.isEmpty(userIds)) {
return;
}
// 2. 执行抄送
FlowElement currentFlowElement = execution.getCurrentFlowElement();
processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(),
currentFlowElement.getId(), null, currentFlowElement.getName());
processInstanceCopyService.createProcessInstanceCopy(userIds, null, execution.getProcessInstanceId(),
currentFlowElement.getId(), currentFlowElement.getName(), null);
}
}

View File

@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.bpm.enums.definition.BpmBoundaryEventType;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
import cn.iocoder.yudao.module.bpm.service.task.BpmActivityService;
import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
import com.google.common.collect.ImmutableSet;
import lombok.extern.slf4j.Slf4j;
@ -44,9 +43,6 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
@Resource
@Lazy // 解决循环依赖
private BpmTaskService taskService;
@Resource
@Lazy // 解决循环依赖
private BpmActivityService activityService;
public static final Set<FlowableEngineEventType> TASK_EVENTS = ImmutableSet.<FlowableEngineEventType>builder()
.add(FlowableEngineEventType.TASK_CREATED)
@ -72,7 +68,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
@Override
protected void activityCancelled(FlowableActivityCancelledEvent event) {
List<HistoricActivityInstance> activityList = activityService.getHistoricActivityListByExecutionId(event.getExecutionId());
List<HistoricActivityInstance> activityList = taskService.getHistoricActivityListByExecutionId(event.getExecutionId());
if (CollUtil.isEmpty(activityList)) {
log.error("[activityCancelled][使用 executionId({}) 查找不到对应的活动实例]", event.getExecutionId());
return;
@ -87,6 +83,7 @@ public class BpmTaskEventListener extends AbstractFlowableEngineEventListener {
}
@Override
@SuppressWarnings("PatternVariableCanBeUsed")
protected void timerFired(FlowableEngineEntityEvent event) {
// 1.1 只处理 BoundaryEvent 边界计时时间
String processDefinitionId = event.getProcessDefinitionId();

View File

@ -3,27 +3,116 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskApproveTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignEmptyHandlerTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskRejectHandlerType;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.impl.util.io.BytesStreamSource;
import java.util.*;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
/**
*
* BPMN Model
*
* 1. BPMN +
* 2. BPMN
* 3. BPMN
* 4. BPMN
*
* @author
*/
@Slf4j
public class BpmnModelUtils {
// ========== BPMN 修改 + 解析元素相关的方法 ==========
public static void addExtensionElement(FlowElement element, String name, String value) {
if (value == null) {
return;
}
ExtensionElement extensionElement = new ExtensionElement();
extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
extensionElement.setElementText(value);
extensionElement.setName(name);
element.addExtensionElement(extensionElement);
}
public static void addExtensionElement(FlowElement element, String name, Integer value) {
if (value == null) {
return;
}
addExtensionElement(element, name, String.valueOf(value));
}
public static void addExtensionElement(FlowElement element, String name, Map<String, String> attributes) {
if (attributes == null) {
return;
}
ExtensionElement extensionElement = new ExtensionElement();
extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
extensionElement.setName(name);
attributes.forEach((key, value) -> {
ExtensionAttribute extensionAttribute = new ExtensionAttribute(key, value);
extensionAttribute.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
extensionElement.addAttribute(extensionAttribute);
});
element.addExtensionElement(extensionElement);
}
/**
*
*
* @param flowElement
* @param elementName
* @return
*/
public static String parseExtensionElement(FlowElement flowElement, String elementName) {
if (flowElement == null) {
return null;
}
ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName));
return element != null ? element.getElementText() : null;
}
/**
*
*
* @param candidateStrategy
* @param candidateParam
* @param flowElement
*/
public static void addCandidateElements(Integer candidateStrategy, String candidateParam, FlowElement flowElement) {
addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY,
candidateStrategy == null ? null : candidateStrategy.toString());
addExtensionElement(flowElement, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, candidateParam);
}
/**
*
*
* @param userTask
* @return
*/
public static Integer parseCandidateStrategy(FlowElement userTask) {
Integer candidateStrategy = NumberUtils.parseInt(userTask.getAttributeValue(
BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY));
@ -35,6 +124,12 @@ public class BpmnModelUtils {
return candidateStrategy;
}
/**
*
*
* @param userTask
* @return
*/
public static String parseCandidateParam(FlowElement userTask) {
String candidateParam = userTask.getAttributeValue(
BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM);
@ -45,39 +140,130 @@ public class BpmnModelUtils {
return candidateParam;
}
/**
*
*
* @see BpmUserTaskApproveTypeEnum
* @param userTask
* @return
*/
public static Integer parseApproveType(FlowElement userTask) {
return NumberUtils.parseInt(parseExtensionElement(userTask, BpmnModelConstants.USER_TASK_APPROVE_TYPE));
}
/**
*
*
* @param rejectHandler
* @param userTask
*/
public static void addTaskRejectElements(BpmSimpleModelNodeVO.RejectHandler rejectHandler, UserTask userTask) {
if (rejectHandler == null) {
return;
}
addExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE, StrUtil.toStringOrNull(rejectHandler.getType()));
addExtensionElement(userTask, USER_TASK_REJECT_RETURN_TASK_ID, rejectHandler.getReturnNodeId());
}
/**
*
*
* @param userTask
* @return
*/
public static BpmUserTaskRejectHandlerType parseRejectHandlerType(FlowElement userTask) {
Integer rejectHandlerType = NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_REJECT_HANDLER_TYPE));
return BpmUserTaskRejectHandlerType.typeOf(rejectHandlerType);
}
/**
* ID
*
* @param flowElement
* @return ID
*/
public static String parseReturnTaskId(FlowElement flowElement) {
return parseExtensionElement(flowElement, USER_TASK_REJECT_RETURN_TASK_ID);
}
/**
*
*
* @see BpmUserTaskAssignStartUserHandlerTypeEnum
* @param assignStartUserHandlerType
* @param userTask
*/
public static void addAssignStartUserHandlerType(Integer assignStartUserHandlerType, UserTask userTask) {
if (assignStartUserHandlerType == null) {
return;
}
addExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE, assignStartUserHandlerType.toString());
}
/**
*
*
* @see BpmUserTaskAssignEmptyHandlerTypeEnum
* @param emptyHandler
* @param userTask
*/
public static void addAssignEmptyHandlerType(BpmSimpleModelNodeVO.AssignEmptyHandler emptyHandler, UserTask userTask) {
if (emptyHandler == null) {
return;
}
addExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE, StrUtil.toStringOrNull(emptyHandler.getType()));
addExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS, StrUtil.join(",", emptyHandler.getUserIds()));
}
/**
*
*
* @param userTask
* @return
*/
public static Integer parseAssignStartUserHandlerType(FlowElement userTask) {
return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_START_USER_HANDLER_TYPE));
}
/**
*
*
* @param userTask
* @return
*/
public static Integer parseAssignEmptyHandlerType(FlowElement userTask) {
return NumberUtils.parseInt(parseExtensionElement(userTask, USER_TASK_ASSIGN_EMPTY_HANDLER_TYPE));
}
/**
* ID
*
* @param userTask
* @return ID
*/
public static List<Long> parseAssignEmptyHandlerUserIds(FlowElement userTask) {
return StrUtils.splitToLong(parseExtensionElement(userTask, USER_TASK_ASSIGN_USER_IDS), ",");
}
public static String parseExtensionElement(FlowElement flowElement, String elementName) {
if (flowElement == null) {
return null;
/**
*
*
* @param fieldsPermissions
* @param flowElement
*/
public static void addFormFieldsPermission(List<Map<String, String>> fieldsPermissions, FlowElement flowElement) {
if (CollUtil.isNotEmpty(fieldsPermissions)) {
fieldsPermissions.forEach(item -> addExtensionElement(flowElement, FORM_FIELD_PERMISSION_ELEMENT, item));
}
ExtensionElement element = CollUtil.getFirst(flowElement.getExtensionElements().get(elementName));
return element != null ? element.getElementText() : null;
}
/**
*
*
* @param bpmnModel bpmnModel
* @param flowElementId ID
* @return
*/
public static Map<String, String> parseFormFieldsPermission(BpmnModel bpmnModel, String flowElementId) {
if (bpmnModel == null || StrUtil.isEmpty(flowElementId)) {
return null;
@ -101,6 +287,29 @@ public class BpmnModelUtils {
return fieldsPermission;
}
/**
*
*/
public static void addButtonsSetting(List<BpmSimpleModelNodeVO.OperationButtonSetting> buttonsSetting, UserTask userTask) {
if (CollUtil.isNotEmpty(buttonsSetting)) {
List<Map<String, String>> list = CollectionUtils.convertList(buttonsSetting, item -> {
Map<String, String> settingMap = Maps.newHashMapWithExpectedSize(3);
settingMap.put(BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE, String.valueOf(item.getId()));
settingMap.put(BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE, item.getDisplayName());
settingMap.put(BUTTON_SETTING_ELEMENT_ENABLE_ATTRIBUTE, String.valueOf(item.getEnable()));
return settingMap;
});
list.forEach(item -> addExtensionElement(userTask, BUTTON_SETTING_ELEMENT, item));
}
}
/**
*
*
* @param bpmnModel bpmnModel
* @param flowElementId ID
* @return
*/
public static Map<Integer, BpmTaskRespVO.OperationButtonSetting> parseButtonsSetting(BpmnModel bpmnModel, String flowElementId) {
FlowElement flowElement = getFlowElementById(bpmnModel, flowElementId);
if (flowElement == null) {
@ -110,7 +319,7 @@ public class BpmnModelUtils {
if (CollUtil.isEmpty(extensionElements)) {
return null;
}
Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = MapUtil.newHashMap(16);
Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonSettings = Maps.newHashMapWithExpectedSize(extensionElements.size());
extensionElements.forEach(element -> {
String id = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_ID_ATTRIBUTE);
String displayName = element.getAttributeValue(FLOWABLE_EXTENSIONS_NAMESPACE, BUTTON_SETTING_ELEMENT_DISPLAY_NAME_ATTRIBUTE);
@ -123,6 +332,23 @@ public class BpmnModelUtils {
return buttonSettings;
}
/**
*
*
* @param boundaryEvent
* @param customElement
* @return
*/
public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) {
if (boundaryEvent == null) {
return null;
}
ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement));
return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null);
}
// ========== BPM 简单查找相关的方法 ==========
/**
* 线
*
@ -168,15 +394,14 @@ public class BpmnModelUtils {
* @param clazz {@link UserTask}{@link Gateway}
* @return
*/
@SuppressWarnings("unchecked")
public static <T extends FlowElement> List<T> getBpmnModelElements(BpmnModel model, Class<T> clazz) {
List<T> result = new ArrayList<>();
model.getProcesses().forEach(process -> {
process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
});
});
model.getProcesses().forEach(process -> process.getFlowElements().forEach(flowElement -> {
if (flowElement.getClass().isAssignableFrom(clazz)) {
result.add((T) flowElement);
}
}));
return result;
}
@ -193,7 +418,7 @@ public class BpmnModelUtils {
public static EndEvent getEndEvent(BpmnModel model) {
Process process = model.getMainProcess();
// 从 flowElementList 找 endEvent. TODO 多个 EndEvent 会有问题
// 从 flowElementList 找 endEvent
return (EndEvent) CollUtil.findOne(process.getFlowElements(), flowElement -> flowElement instanceof EndEvent);
}
@ -221,7 +446,7 @@ public class BpmnModelUtils {
return StrUtil.utf8Str(bpmnBytes);
}
// ========== 遍历相关的方法 ==========
// ========== BPMN 复杂遍历相关的方法 ==========
/**
* source
@ -316,16 +541,16 @@ public class BpmnModelUtils {
return userTaskList;
}
/**
*
* 退
* 退
*
* @param source
* @param target
* @param visitedElements 线 ID线
* @return
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
public static boolean isSequentialReachable(FlowElement source, FlowElement target, Set<String> visitedElements) {
visitedElements = visitedElements == null ? new HashSet<>() : visitedElements;
// 不能是开始事件和子流程
@ -436,11 +661,136 @@ public class BpmnModelUtils {
return userTaskList;
}
public static String parseBoundaryEventExtensionElement(BoundaryEvent boundaryEvent, String customElement) {
if (boundaryEvent == null) {
return null;
}
ExtensionElement extensionElement = CollUtil.getFirst(boundaryEvent.getExtensionElements().get(customElement));
return Optional.ofNullable(extensionElement).map(ExtensionElement::getElementText).orElse(null);
// ========== BPMN 流程预测相关的方法 ==========
/**
* StartEventUserTaskServiceTaskEndEvent List
*
* @param bpmnModel BPMN
* @param variables
* @return
*/
public static List<FlowElement> simulateProcess(BpmnModel bpmnModel, Map<String, Object> variables) {
List<FlowElement> resultElements = new ArrayList<>();
Set<FlowElement> visitElements = new HashSet<>();
// 从 StartEvent 开始遍历
StartEvent startEvent = getStartEvent(bpmnModel);
simulateNextFlowElements(startEvent, variables, resultElements, visitElements);
// 将 EndEvent 放在末尾。原因是DFS 遍历,可能 EndEvent 在 resultElements 中
List<FlowElement> endEvents = CollUtil.removeWithAddIf(resultElements,
flowElement -> flowElement instanceof EndEvent);
resultElements.addAll(endEvents);
return resultElements;
}
@SuppressWarnings("PatternVariableCanBeUsed")
private static void simulateNextFlowElements(FlowElement currentElement, Map<String, Object> variables,
List<FlowElement> resultElements, Set<FlowElement> visitElements) {
// 如果为空,或者已经遍历过,则直接结束
if (currentElement == null) {
return;
}
if (visitElements.contains(currentElement)) {
return;
}
visitElements.add(currentElement);
// 情况StartEvent/EndEvent/UserTask/ServiceTask
if (currentElement instanceof StartEvent
|| currentElement instanceof EndEvent
|| currentElement instanceof UserTask
|| currentElement instanceof ServiceTask) {
// 添加元素
FlowNode flowNode = (FlowNode) currentElement;
resultElements.add(flowNode);
// 遍历子节点
flowNode.getOutgoingFlows().forEach(
nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
return;
}
// 情况ExclusiveGateway 排它,只有一个满足条件的。如果没有,就走默认的
if (currentElement instanceof ExclusiveGateway) {
// 查找满足条件的 SequenceFlow 路径
Gateway gateway = (Gateway) currentElement;
SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& evalConditionExpress(variables, flow.getConditionExpression()));
if (matchSequenceFlow == null) {
matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
// 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
if (matchSequenceFlow == null && gateway.getOutgoingFlows().size() == 1) {
matchSequenceFlow = gateway.getOutgoingFlows().get(0);
}
}
// 遍历满足条件的 SequenceFlow 路径
if (matchSequenceFlow != null) {
simulateNextFlowElements(matchSequenceFlow.getTargetFlowElement(), variables, resultElements, visitElements);
}
return;
}
// 情况InclusiveGateway 包容,多个满足条件的。如果没有,就走默认的
if (currentElement instanceof InclusiveGateway) {
// 查找满足条件的 SequenceFlow 路径
Gateway gateway = (Gateway) currentElement;
Collection<SequenceFlow> matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& evalConditionExpress(variables, flow.getConditionExpression()));
if (CollUtil.isEmpty(matchSequenceFlows)) {
matchSequenceFlows = CollUtil.filterNew(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId()));
// 特殊:没有默认的情况下,并且只有 1 个条件,则认为它是默认的
if (CollUtil.isEmpty(matchSequenceFlows) && gateway.getOutgoingFlows().size() == 1) {
matchSequenceFlows = gateway.getOutgoingFlows();
}
}
// 遍历满足条件的 SequenceFlow 路径
matchSequenceFlows.forEach(
flow -> simulateNextFlowElements(flow.getTargetFlowElement(), variables, resultElements, visitElements));
}
// 情况ParallelGateway 并行,都满足,都走
if (currentElement instanceof ParallelGateway) {
Gateway gateway = (Gateway) currentElement;
// 遍历子节点
gateway.getOutgoingFlows().forEach(
nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
return;
}
}
/**
* true
*
* @param variables
* @param express
* @return
*/
public static boolean evalConditionExpress(Map<String, Object> variables, String express) {
if (express == null) {
return Boolean.FALSE;
}
try {
Object result = FlowableUtils.getExpressionValue(variables, express);
return Boolean.TRUE.equals(result);
} catch (FlowableException ex) {
log.error("[evalConditionExpress][条件表达式({}) 变量({}) 解析报错", express, variables, ex);
return Boolean.FALSE;
}
}
@SuppressWarnings("PatternVariableCanBeUsed")
public static boolean isSequentialUserTask(FlowElement flowElement) {
if (!(flowElement instanceof UserTask)) {
return false;
}
UserTask userTask = (UserTask) flowElement;
MultiInstanceLoopCharacteristics loopCharacteristics = userTask.getLoopCharacteristics();
return loopCharacteristics != null && loopCharacteristics.isSequential();
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableConstants;
@ -8,6 +9,8 @@ import org.flowable.common.engine.api.delegate.Expression;
import org.flowable.common.engine.api.variable.VariableContainer;
import org.flowable.common.engine.impl.el.ExpressionManager;
import org.flowable.common.engine.impl.identity.Authentication;
import org.flowable.common.engine.impl.variable.MapDelegateVariableContainer;
import org.flowable.engine.ManagementService;
import org.flowable.engine.ProcessEngineConfiguration;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl;
@ -94,6 +97,27 @@ public class FlowableUtils {
return (Integer) processVariables.get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_STATUS);
}
/**
*
*
* @param processInstance
* @return
*/
public static String getProcessInstanceReason(HistoricProcessInstance processInstance) {
return (String) processInstance.getProcessVariables().get(BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_REASON);
}
/**
*
*
* @param processInstance
* @return
*/
public static Map<String, Object> getProcessInstanceFormVariable(ProcessInstance processInstance) {
Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables());
return filterProcessInstanceFormVariable(processVariables);
}
/**
*
*
@ -101,9 +125,8 @@ public class FlowableUtils {
* @return
*/
public static Map<String, Object> getProcessInstanceFormVariable(HistoricProcessInstance processInstance) {
Map<String, Object> formVariables = new HashMap<>(processInstance.getProcessVariables());
filterProcessInstanceFormVariable(formVariables);
return formVariables;
Map<String, Object> processVariables = new HashMap<>(processInstance.getProcessVariables());
return filterProcessInstanceFormVariable(processVariables);
}
/**
@ -125,9 +148,22 @@ public class FlowableUtils {
* @param processInstance
* @return Map
*/
@SuppressWarnings("unchecked")
public static Map<String, List<Long>> getStartUserSelectAssignees(ProcessInstance processInstance) {
return (Map<String, List<Long>>) processInstance.getProcessVariables().get(
return processInstance != null ? getStartUserSelectAssignees(processInstance.getProcessVariables()) : null;
}
/**
* Map
*
* @param processVariables
* @return Map
*/
@SuppressWarnings("unchecked")
public static Map<String, List<Long>> getStartUserSelectAssignees(Map<String, Object> processVariables) {
if (processVariables == null) {
return null;
}
return (Map<String, List<Long>>) processVariables.get(
BpmnVariableConstants.PROCESS_INSTANCE_VARIABLE_START_USER_SELECT_ASSIGNEES);
}
@ -181,13 +217,30 @@ public class FlowableUtils {
// ========== Expression 相关的工具方法 ==========
public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) {
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
assert processEngineConfiguration != null;
private static Object getExpressionValue(VariableContainer variableContainer, String expressionString,
ProcessEngineConfigurationImpl processEngineConfiguration) {
assert processEngineConfiguration!= null;
ExpressionManager expressionManager = processEngineConfiguration.getExpressionManager();
assert expressionManager != null;
assert expressionManager!= null;
Expression expression = expressionManager.createExpression(expressionString);
return expression.getValue(variableContainer);
}
public static Object getExpressionValue(VariableContainer variableContainer, String expressionString) {
ProcessEngineConfigurationImpl processEngineConfiguration = CommandContextUtil.getProcessEngineConfiguration();
if (processEngineConfiguration != null) {
return getExpressionValue(variableContainer, expressionString, processEngineConfiguration);
}
// 如果 ProcessEngineConfigurationImpl 获取不到,则需要通过 ManagementService 来获取
ManagementService managementService = SpringUtil.getBean(ManagementService.class);
assert managementService != null;
return managementService.executeCommand(context ->
getExpressionValue(variableContainer, expressionString, CommandContextUtil.getProcessEngineConfiguration()));
}
public static Object getExpressionValue(Map<String, Object> variable, String expressionString) {
VariableContainer variableContainer = new MapDelegateVariableContainer(variable, VariableContainer.empty());
return getExpressionValue(variableContainer, expressionString);
}
}

View File

@ -82,4 +82,11 @@ public interface BpmCategoryService {
*/
List<BpmCategoryDO> getCategoryListByStatus(Integer status);
/**
* sort 0
*
* @param ids
*/
void updateCategorySortBatch(List<Long> ids);
}

View File

@ -9,12 +9,15 @@ import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.category.BpmCa
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.category.BpmCategoryMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
@ -56,7 +59,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
private void validateCategoryNameUnique(BpmCategorySaveReqVO updateReqVO) {
BpmCategoryDO category = bpmCategoryMapper.selectByName(updateReqVO.getName());
if (category == null
|| ObjUtil.equal(category.getId(), updateReqVO.getId())) {
|| ObjUtil.equal(category.getId(), updateReqVO.getId())) {
return;
}
throw exception(CATEGORY_NAME_DUPLICATE, updateReqVO.getName());
@ -65,7 +68,7 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
private void validateCategoryCodeUnique(BpmCategorySaveReqVO updateReqVO) {
BpmCategoryDO category = bpmCategoryMapper.selectByCode(updateReqVO.getCode());
if (category == null
|| ObjUtil.equal(category.getId(), updateReqVO.getId())) {
|| ObjUtil.equal(category.getId(), updateReqVO.getId())) {
return;
}
throw exception(CATEGORY_CODE_DUPLICATE, updateReqVO.getCode());
@ -108,4 +111,20 @@ public class BpmCategoryServiceImpl implements BpmCategoryService {
return bpmCategoryMapper.selectListByStatus(status);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateCategorySortBatch(List<Long> ids) {
// 校验分类都存在
List<BpmCategoryDO> categories = bpmCategoryMapper.selectByIds(ids);
if (categories.size() != ids.size()) {
throw exception(CATEGORY_NOT_EXISTS);
}
// 批量更新排序
List<BpmCategoryDO> updateList = IntStream.range(0, ids.size())
.mapToObj(index -> new BpmCategoryDO().setId(ids.get(index)).setSort(index))
.collect(Collectors.toList());
bpmCategoryMapper.updateBatch(updateList);
}
}

View File

@ -1,29 +1,28 @@
package cn.iocoder.yudao.module.bpm.service.definition;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
import jakarta.validation.Valid;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.engine.repository.Model;
import javax.validation.Valid;
import java.util.List;
/**
* Flowable
*
*
* @author yunlongn
*/
public interface BpmModelService {
/**
*
*
*
* @param pageVO
* @return
* @param name
* @return
*/
PageResult<Model> getModelPage(BpmModelPageReqVO pageVO);
List<Model> getModelList(String name);
/**
*
@ -65,6 +64,14 @@ public interface BpmModelService {
*/
void updateModel(Long userId, @Valid BpmModelSaveReqVO updateReqVO);
/**
*
*
* @param userId
* @param ids
*/
void updateModelSortBatch(Long userId, List<String> ids);
/**
*
*

View File

@ -3,12 +3,9 @@ package cn.iocoder.yudao.module.bpm.service.definition;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelMetaInfoVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.BpmModelSaveReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelUpdateReqVO;
@ -35,14 +32,15 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.*;
/**
* Flowable
* Flowable {@link Model}
* Flowable {@link Model}
*
* @author yunlongn
* @author
@ -64,27 +62,12 @@ public class BpmModelServiceImpl implements BpmModelService {
private BpmTaskCandidateInvoker taskCandidateInvoker;
@Override
public PageResult<Model> getModelPage(BpmModelPageReqVO pageVO) {
public List<Model> getModelList(String name) {
ModelQuery modelQuery = repositoryService.createModelQuery();
modelQuery.modelTenantId(FlowableUtils.getTenantId());
if (StrUtil.isNotBlank(pageVO.getKey())) {
modelQuery.modelKey(pageVO.getKey());
if (StrUtil.isNotEmpty(name)) {
modelQuery.modelNameLike(name);
}
if (StrUtil.isNotBlank(pageVO.getName())) {
modelQuery.modelNameLike("%" + pageVO.getName() + "%"); // 模糊匹配
}
if (StrUtil.isNotBlank(pageVO.getCategory())) {
modelQuery.modelCategory(pageVO.getCategory());
}
// 执行查询
long count = modelQuery.count();
if (count == 0) {
return PageResult.empty(count);
}
List<Model> models = modelQuery
.orderByCreateTime().desc()
.listPage(PageUtils.getStart(pageVO), pageVO.getPageSize());
return new PageResult<>(models, count);
return modelQuery.list();
}
@Override
@ -100,6 +83,7 @@ public class BpmModelServiceImpl implements BpmModelService {
}
// 2.1 创建流程定义
createReqVO.setSort(System.currentTimeMillis()); // 使用当前时间,作为排序
Model model = repositoryService.newModel();
BpmModelConvert.INSTANCE.copyToModel(model, createReqVO);
model.setTenantId(FlowableUtils.getTenantId());
@ -120,6 +104,34 @@ public class BpmModelServiceImpl implements BpmModelService {
repositoryService.saveModel(model);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateModelSortBatch(Long userId, List<String> ids) {
// 1.1 校验流程模型存在
List<Model> models = repositoryService.createModelQuery()
.modelTenantId(FlowableUtils.getTenantId()).list();
models.removeIf(model ->!ids.contains(model.getId()));
if (ids.size() != models.size()) {
throw exception(MODEL_NOT_EXISTS);
}
Map<String, Model> modelMap = convertMap(models, Model::getId);
// 1.2 校验是否为管理员
ids.forEach(id -> validateModelManager(id, userId));
// 保存排序
long sort = System.currentTimeMillis(); // 使用时间戳 - i 作为排序
for (int i = ids.size() - 1; i > 0; i--) {
Model model = modelMap.get(ids.get(i));
// 更新模型
BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model).setSort(sort);
model.setMetaInfo(JsonUtils.toJsonString(metaInfo));
repositoryService.saveModel(model);
// 更新排序
processDefinitionService.updateProcessDefinitionSortByModelId(model.getId(), sort);
sort--;
}
}
private Model validateModelExists(String id) {
Model model = repositoryService.getModel(id);
if (model == null) {
@ -139,7 +151,7 @@ public class BpmModelServiceImpl implements BpmModelService {
Model model = validateModelExists(id);
BpmModelMetaInfoVO metaInfo = BpmModelConvert.INSTANCE.parseMetaInfo(model);
if (metaInfo == null || !CollUtil.contains(metaInfo.getManagerUserIds(), userId)) {
throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER);
throw exception(MODEL_UPDATE_FAIL_NOT_MANAGER, model.getName());
}
return model;
}
@ -158,10 +170,10 @@ public class BpmModelServiceImpl implements BpmModelService {
// 1.4 校验任务分配规则已配置
taskCandidateInvoker.validateBpmnConfig(bpmnBytes);
// 1.5 获取仿钉钉流程设计器模型数据
byte[] simpleBytes = getModelSimpleJson(model.getId());
String simpleJson = getModelSimpleJson(model.getId());
// 2.1 创建流程定义
String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleBytes, form);
String definitionId = processDefinitionService.createProcessDefinition(model, metaInfo, bpmnBytes, simpleJson, form);
// 2.2 将老的流程定义进行挂起。也就是说,只有最新部署的流程定义,才可以发起任务。
updateProcessDefinitionSuspended(model.getDeploymentId());
@ -226,8 +238,8 @@ public class BpmModelServiceImpl implements BpmModelService {
public BpmSimpleModelNodeVO getSimpleModel(String modelId) {
Model model = validateModelExists(modelId);
// 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ ,获取仿钉钉快搭模型的 JSON 数据
byte[] jsonBytes = getModelSimpleJson(model.getId());
return JsonUtils.parseObject(jsonBytes, BpmSimpleModelNodeVO.class);
String json = getModelSimpleJson(model.getId());
return JsonUtils.parseObject(json, BpmSimpleModelNodeVO.class);
}
@Override
@ -240,7 +252,7 @@ public class BpmModelServiceImpl implements BpmModelService {
// 2.2 保存 Bpmn XML
updateModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel));
// 2.3 保存 JSON 数据
saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModel()));
updateModelSimpleJson(model.getId(), reqVO.getSimpleModel());
}
/**
@ -279,15 +291,21 @@ public class BpmModelServiceImpl implements BpmModelService {
repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml));
}
private byte[] getModelSimpleJson(String id) {
return repositoryService.getModelEditorSourceExtra(id);
@SuppressWarnings("JavaExistingMethodCanBeUsed")
private String getModelSimpleJson(String id) {
byte[] bytes = repositoryService.getModelEditorSourceExtra(id);
if (ArrayUtil.isEmpty(bytes)) {
return null;
}
return StrUtil.utf8Str(bytes);
}
private void saveModelSimpleJson(String id, byte[] jsonBytes) {
if (ArrayUtil.isEmpty(jsonBytes)) {
private void updateModelSimpleJson(String id, BpmSimpleModelNodeVO node) {
if (node == null) {
return;
}
repositoryService.addModelEditorSourceExtra(id, jsonBytes);
byte[] bytes = JsonUtils.toJsonByte(node);
repositoryService.addModelEditorSourceExtra(id, bytes);
}
/**

View File

@ -18,7 +18,7 @@ import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
/**
* Flowable
*
*
* @author yunlong.li
* @author ZJQ
@ -48,12 +48,12 @@ public interface BpmProcessDefinitionService {
* @param model
* @param modelMetaInfo
* @param bpmnBytes BPMN XML
* @param simpleBytes SIMPLE Model JSON
* @param simpleJson SIMPLE Model JSON
* @param form
* @return
*/
String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo,
byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form);
byte[] bpmnBytes, String simpleJson, BpmFormDO form);
/**
*
@ -63,6 +63,14 @@ public interface BpmProcessDefinitionService {
*/
void updateProcessDefinitionState(String id, Integer state);
/**
*
*
* @param modelId
* @param sort
*/
void updateProcessDefinitionSortByModelId(String modelId, Long sort);
/**
* BPMN
*

View File

@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitio
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionInfoMapper;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.common.engine.impl.db.SuspensionState;
@ -23,8 +24,6 @@ import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@ -120,7 +119,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
@Override
public String createProcessDefinition(Model model, BpmModelMetaInfoVO modelMetaInfo,
byte[] bpmnBytes, byte[] simpleBytes, BpmFormDO form) {
byte[] bpmnBytes, String simpleJson, BpmFormDO form) {
// 创建 Deployment 部署
Deployment deploy = repositoryService.createDeployment()
.key(model.getKey()).name(model.getName()).category(model.getCategory())
@ -145,8 +144,8 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
// 插入拓展表
BpmProcessDefinitionInfoDO definitionDO = BeanUtils.toBean(modelMetaInfo, BpmProcessDefinitionInfoDO.class)
.setModelId(model.getId()).setProcessDefinitionId(definition.getId()).setModelType(modelMetaInfo.getType())
.setSimpleModel(StrUtil.str(simpleBytes, StandardCharsets.UTF_8));
.setModelId(model.getId()).setProcessDefinitionId(definition.getId())
.setModelType(modelMetaInfo.getType()).setSimpleModel(simpleJson);
if (form != null) {
definitionDO.setFormFields(form.getFields()).setFormConf(form.getConf());
@ -172,6 +171,11 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
log.error("[updateProcessDefinitionState][流程定义({}) 修改未知状态({})]", id, state);
}
@Override
public void updateProcessDefinitionSortByModelId(String modelId, Long sort) {
processDefinitionMapper.updateByModelId(modelId, new BpmProcessDefinitionInfoDO().setSort(sort));
}
@Override
public BpmnModel getProcessDefinitionBpmnModel(String id) {
return repositoryService.getBpmnModel(id);

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task;
import org.flowable.engine.history.HistoricActivityInstance;
import java.util.List;
/**
* BPM Service
*
* @author
*/
public interface BpmActivityService {
/**
*
*
* @param processInstanceId
* @return
*/
List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId);
/**
*
*
* @param executionId
* @return
*/
List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId);
}

View File

@ -1,37 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.HistoryService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.List;
/**
* BPM Service
*
* @author
*/
@Service
@Slf4j
@Validated
public class BpmActivityServiceImpl implements BpmActivityService {
@Resource
private HistoryService historyService;
@Override
public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) {
return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime().asc().list();
}
@Override
public List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId) {
return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list();
}
}

View File

@ -3,9 +3,10 @@ package cn.iocoder.yudao.module.bpm.service.task;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
import jakarta.validation.constraints.NotEmpty;
import org.flowable.bpmn.model.FlowNode;
import java.util.Collection;
import java.util.Set;
/**
* Service
@ -15,24 +16,29 @@ import java.util.Set;
public interface BpmProcessInstanceCopyService {
/**
*
*
*
* @param userIds
* @param reason
* @param taskId
*/
void createProcessInstanceCopy(Collection<Long> userIds, String taskId);
void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId);
/**
*
*
*
* @param userIds
* @param reason
* @param processInstanceId
* @param activityId id ( BPMN XML Id)
* // TODO 芋艿这个 taskId 是不是可以不要了
* @param taskId
* @param taskName
* @param activityId {@link FlowNode#getId()}
* @param activityName {@link FlowNode#getName()}
* @param taskId
*/
void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String activityId, String taskId, String taskName);
void createProcessInstanceCopy(Collection<Long> userIds, String reason,
@NotEmpty(message = "流程实例编号不能为空") String processInstanceId,
@NotEmpty(message = "流程活动编号不能为空") String activityId,
@NotEmpty(message = "流程活动名字不能为空") String activityName,
String taskId);
/**
*
@ -43,14 +49,5 @@ public interface BpmProcessInstanceCopyService {
*/
PageResult<BpmProcessInstanceCopyDO> getProcessInstanceCopyPage(Long userId,
BpmProcessInstanceCopyPageReqVO pageReqVO);
// TODO @芋艿:重点在 review 下
/**
* Id
*
* @param processInstanceId Id
* @param activityId Id
* @return Ids
*/
Set<Long> getCopyUserIds(String processInstanceId, String activityId);
}

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.bpm.service.task;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.task.BpmProcessInstanceCopyMapper;
@ -19,7 +18,6 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@ -49,17 +47,19 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
private BpmProcessDefinitionService processDefinitionService;
@Override
public void createProcessInstanceCopy(Collection<Long> userIds, String taskId) {
public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String taskId) {
Task task = taskService.getTask(taskId);
if (ObjectUtil.isNull(task)) {
throw exception(ErrorCodeConstants.TASK_NOT_EXISTS);
}
String processInstanceId = task.getProcessInstanceId();
createProcessInstanceCopy(userIds, processInstanceId, task.getTaskDefinitionKey(), task.getId(), task.getName());
// 执行抄送
createProcessInstanceCopy(userIds, reason,
task.getProcessInstanceId(), task.getTaskDefinitionKey(), task.getId(), task.getName());
}
@Override
public void createProcessInstanceCopy(Collection<Long> userIds, String processInstanceId, String activityId, String taskId, String taskName) {
public void createProcessInstanceCopy(Collection<Long> userIds, String reason, String processInstanceId,
String activityId, String activityName, String taskId) {
// 1.1 校验流程实例存在
ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId);
if (processInstance == null) {
@ -74,10 +74,10 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
// 2. 创建抄送流程
List<BpmProcessInstanceCopyDO> copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO()
.setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId()))
.setUserId(userId).setReason(reason).setStartUserId(Long.valueOf(processInstance.getStartUserId()))
.setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName())
.setCategory(processDefinition.getCategory()).setActivityId(activityId)
.setTaskId(taskId).setTaskName(taskName));
.setCategory(processDefinition.getCategory()).setTaskId(taskId)
.setActivityId(activityId).setActivityName(activityName));
processInstanceCopyMapper.insertBatch(copyList);
}
@ -87,10 +87,4 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy
return processInstanceCopyMapper.selectPage(userId, pageReqVO);
}
@Override
public Set<Long> getCopyUserIds(String processInstanceId, String activityId) {
return CollectionUtils.convertSet(processInstanceCopyMapper.selectListByProcessInstanceIdAndActivityId(processInstanceId, activityId),
BpmProcessInstanceCopyDO::getUserId);
}
}

View File

@ -84,14 +84,6 @@ public interface BpmProcessInstanceService {
PageResult<HistoricProcessInstance> getProcessInstancePage(Long userId,
@Valid BpmProcessInstancePageReqVO pageReqVO);
/**
*
*
* @param reqVO
* @return
*/
Map<String, String> getFormFieldsPermission(@Valid BpmFormFieldsPermissionReqVO reqVO);
// TODO @芋艿:重点在 review 下
/**
*
@ -104,6 +96,14 @@ public interface BpmProcessInstanceService {
*/
BpmApprovalDetailRespVO getApprovalDetail(Long loginUserId, @Valid BpmApprovalDetailReqVO reqVO);
/**
* BPMN
*
* @param id
* @return BPMN
*/
BpmProcessInstanceBpmnModelViewRespVO getProcessInstanceBpmnModelView(String id);
// ========== Update 写入相关方法 ==========
/**
@ -157,5 +157,4 @@ public interface BpmProcessInstanceService {
*/
void processProcessInstanceCompleted(ProcessInstance instance);
}

View File

@ -5,7 +5,9 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.*;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskTimeoutHandlerTypeEnum;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance;
import javax.validation.Valid;
@ -32,6 +34,15 @@ public interface BpmTaskService {
*/
PageResult<Task> getTaskTodoPage(Long userId, BpmTaskPageReqVO pageReqVO);
/**
*
*
* @param userId
* @param processInstanceId
* @return
*/
BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId);
/**
*
*
@ -73,9 +84,10 @@ public interface BpmTaskService {
*
*
* @param processInstanceId
* @param asc
* @return
*/
List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId);
List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc);
/**
*
@ -105,21 +117,30 @@ public interface BpmTaskService {
*
*
* @param processInstanceId
* @param assigned
* @param taskDefineKey Key
* @param assigned
* @param taskDefineKey Key
*/
List<Task> getRunningTaskListByProcessInstanceId(String processInstanceId,
Boolean assigned,
String taskDefineKey);
/**
* 退 UserTask
* 退 UserTask
*
* @param id ID
* @return 退
* @return 退
*/
List<UserTask> getUserTaskListByReturn(String id);
/**
*
*
* @param parentTaskId ID
* @param tasks
* @return
*/
<T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks);
/**
*
*
@ -129,12 +150,20 @@ public interface BpmTaskService {
List<Task> getTaskListByParentTaskId(String parentTaskId);
/**
* ID Map
*
*
* @param taskIds ID
* @return ID Map
* @param processInstanceId
* @return
*/
Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds);
List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId);
/**
*
*
* @param executionId
* @return
*/
List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId);
// ========== Update 写入相关方法 ==========
@ -170,10 +199,10 @@ public interface BpmTaskService {
void moveTaskToEnd(String processInstanceId);
/**
* 退 targetDefinitionKey
* 退 targetDefinitionKey
*
* @param userId
* @param reqVO 退keyID
* @param reqVO 退keyID
*/
void returnTask(Long userId, BpmTaskReturnReqVO reqVO);
@ -201,14 +230,22 @@ public interface BpmTaskService {
*/
void deleteSignTask(Long userId, BpmTaskSignDeleteReqVO reqVO);
/**
*
*
* @param userId
* @param reqVO
*/
void copyTask(Long userId, @Valid BpmTaskCopyReqVO reqVO);
// ========== Event 事件相关方法 ==========
/**
* Task
*
* <p>
* 1.
* 2. 12
*
* <p>
* {@link #processTaskAssigned(Task)}
*
* @param task
@ -233,8 +270,8 @@ public interface BpmTaskService {
* Task
*
* @param processInstanceId
* @param taskDefineKey Key
* @param handlerType {@link BpmUserTaskTimeoutHandlerTypeEnum}
* @param taskDefineKey Key
* @param handlerType {@link BpmUserTaskTimeoutHandlerTypeEnum}
*/
void processTaskTimeout(String processInstanceId, String taskDefineKey, Integer handlerType);

View File

@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnVariableCon
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.service.definition.BpmModelService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@ -36,9 +37,11 @@ import org.flowable.engine.HistoryService;
import org.flowable.engine.ManagementService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricActivityInstance;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
@ -81,6 +84,8 @@ public class BpmTaskServiceImpl implements BpmTaskService {
@Resource
private BpmProcessInstanceService processInstanceService;
@Resource
private BpmProcessDefinitionService bpmProcessDefinitionService;
@Resource
private BpmProcessInstanceCopyService processInstanceCopyService;
@Resource
private BpmModelService modelService;
@ -116,6 +121,41 @@ public class BpmTaskServiceImpl implements BpmTaskService {
return new PageResult<>(tasks, count);
}
@Override
public BpmTaskRespVO getFirstTodoTask(Long userId, String processInstanceId) {
if (processInstanceId == null) {
return null;
}
// 1. 查询所有任务
List<Task> tasks = taskService.createTaskQuery()
.active()
.processInstanceId(processInstanceId)
.includeTaskLocalVariables()
.includeProcessVariables()
.orderByTaskCreateTime().asc() // 按创建时间升序
.list();
if (CollUtil.isEmpty(tasks)) {
return null;
}
// 2.1 查询我的首个任务
Task todoTask = CollUtil.findOne(tasks, task -> {
return isAssignUserTask(userId, task) // 当前用户为审批人
|| isAddSignUserTask(userId, task); // 当前用户为加签人(为了减签)
});
if (todoTask == null) {
return null;
}
// 2.2 查询该任务的子任务
List<Task> childrenTasks = getAllChildrenTaskListByParentTaskId(todoTask.getId(), tasks);
// 3. 转换返回
BpmnModel bpmnModel = bpmProcessDefinitionService.getProcessDefinitionBpmnModel(todoTask.getProcessDefinitionId());
Map<Integer, BpmTaskRespVO.OperationButtonSetting> buttonsSetting = BpmnModelUtils.parseButtonsSetting(
bpmnModel, todoTask.getTaskDefinitionKey());
return BpmTaskConvert.INSTANCE.buildTodoTask(todoTask, childrenTasks, buttonsSetting);
}
@Override
public PageResult<HistoricTaskInstance> getTaskDonePage(Long userId, BpmTaskPageReqVO pageVO) {
HistoricTaskInstanceQuery taskQuery = historyService.createHistoricTaskInstanceQuery()
@ -170,16 +210,16 @@ public class BpmTaskServiceImpl implements BpmTaskService {
}
@Override
public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId) {
List<HistoricTaskInstance> tasks = historyService.createHistoricTaskInstanceQuery()
public List<HistoricTaskInstance> getTaskListByProcessInstanceId(String processInstanceId, Boolean asc) {
HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery()
.includeTaskLocalVariables()
.processInstanceId(processInstanceId)
.orderByHistoricTaskInstanceStartTime().desc() // 创建时间倒序
.list();
if (CollUtil.isEmpty(tasks)) {
return Collections.emptyList();
.processInstanceId(processInstanceId);
if (Boolean.TRUE.equals(asc)) {
query.orderByHistoricTaskInstanceStartTime().asc();
} else {
query.orderByHistoricTaskInstanceStartTime().desc();
}
return tasks;
return query.list();
}
/**
@ -254,11 +294,43 @@ public class BpmTaskServiceImpl implements BpmTaskService {
if (CollUtil.isEmpty(previousUserList)) {
return Collections.emptyList();
}
// 2.2 过滤:只有串行可到达的节点,才可以退。类似非串行、子流程无法退回
// 2.2 过滤:只有串行可到达的节点,才可以退。类似非串行、子流程无法退回
previousUserList.removeIf(userTask -> !BpmnModelUtils.isSequentialReachable(source, userTask, null));
return previousUserList;
}
@Override
public <T extends TaskInfo> List<T> getAllChildrenTaskListByParentTaskId(String parentTaskId, List<T> tasks) {
if (CollUtil.isEmpty(tasks)) {
return Collections.emptyList();
}
Map<String, List<T>> parentTaskMap = convertMultiMap(
filterList(tasks, task -> StrUtil.isNotEmpty(task.getParentTaskId())), TaskInfo::getParentTaskId);
if (CollUtil.isEmpty(parentTaskMap)) {
return Collections.emptyList();
}
List<T> result = new ArrayList<>();
// 1. 递归获取子级
Stack<String> stack = new Stack<>();
stack.push(parentTaskId);
// 2. 递归遍历
for (int i = 0; i < Short.MAX_VALUE; i++) {
if (stack.isEmpty()) {
break;
}
// 2.1 获取子任务们
String taskId = stack.pop();
List<T> childTaskList = filterList(tasks, task -> StrUtil.equals(task.getParentTaskId(), taskId));
// 2.2 如果非空,则添加到 stack 进一步递归
if (CollUtil.isNotEmpty(childTaskList)) {
stack.addAll(convertList(childTaskList, TaskInfo::getId));
result.addAll(childTaskList);
}
}
return result;
}
/**
*
*
@ -331,12 +403,50 @@ public class BpmTaskServiceImpl implements BpmTaskService {
}
@Override
public Map<String, String> getTaskNameByTaskIds(Collection<String> taskIds) {
if (CollUtil.isEmpty(taskIds)) {
return Collections.emptyMap();
}
List<Task> tasks = taskService.createTaskQuery().taskIds(taskIds).list();
return convertMap(tasks, Task::getId, Task::getName);
public List<HistoricActivityInstance> getActivityListByProcessInstanceId(String processInstanceId) {
return historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId)
.orderByHistoricActivityInstanceStartTime().asc().list();
}
@Override
public List<HistoricActivityInstance> getHistoricActivityListByExecutionId(String executionId) {
return historyService.createHistoricActivityInstanceQuery().executionId(executionId).list();
}
/**
*
*
* @param userId
* @param task
* @return
*/
private boolean isAssignUserTask(Long userId, Task task) {
Long assignee = NumberUtil.parseLong(task.getAssignee(), null);
return ObjectUtil.equals(userId, assignee);
}
/**
*
*
* @param userId
* @param task
* @return
*/
private boolean isOwnerUserTask(Long userId, Task task) {
Long assignee = NumberUtil.parseLong(task.getOwner(), null);
return ObjectUtil.equal(userId, assignee);
}
/**
*
*
* @param userId Id
* @param task
* @return
*/
private boolean isAddSignUserTask(Long userId, Task task) {
return (isAssignUserTask(userId, task) || isOwnerUserTask(userId, task))
&& BpmTaskSignTypeEnum.of(task.getScopeType()) != null;
}
// ========== Update 写入相关方法 ==========
@ -352,11 +462,6 @@ public class BpmTaskServiceImpl implements BpmTaskService {
throw exception(PROCESS_INSTANCE_NOT_EXISTS);
}
// 2. 抄送用户
if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) {
processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId());
}
// 情况一:被委派的任务,不调用 complete 去完成任务
if (DelegationState.PENDING.equals(task.getDelegationState())) {
approveDelegateTask(reqVO, task);
@ -370,12 +475,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
}
// 情况三:审批普通的任务。大多数情况下,都是这样
// 3.1 更新 task 状态、原因
// 2.1 更新 task 状态、原因
updateTaskStatusAndReason(task.getId(), BpmTaskStatusEnum.APPROVE.getStatus(), reqVO.getReason());
// 3.2 添加评论
// 2.2 添加评论
taskService.addComment(task.getId(), task.getProcessInstanceId(), BpmCommentTypeEnum.APPROVE.getType(),
BpmCommentTypeEnum.APPROVE.formatComment(reqVO.getReason()));
// 3.3 调用 BPM complete 去完成任务
// 2.3 调用 BPM complete 去完成任务
// 其中variables 是存储动态表单到 local 任务级别。过滤一下,避免 ProcessInstance 系统级的变量被占用
if (CollUtil.isNotEmpty(reqVO.getVariables())) {
Map<String, Object> variables = FlowableUtils.filterTaskFormVariable(reqVO.getVariables());
@ -519,7 +624,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
BpmUserTaskRejectHandlerType userTaskRejectHandlerType = BpmnModelUtils.parseRejectHandlerType(userTaskElement);
if (userTaskRejectHandlerType == BpmUserTaskRejectHandlerType.RETURN_USER_TASK) {
String returnTaskId = BpmnModelUtils.parseReturnTaskId(userTaskElement);
Assert.notNull(returnTaskId, "退的节点不能为空");
Assert.notNull(returnTaskId, "退的节点不能为空");
returnTask(userId, new BpmTaskReturnReqVO().setId(task.getId())
.setTargetTaskDefinitionKey(returnTaskId).setReason(reqVO.getReason()));
return;
@ -563,12 +668,12 @@ public class BpmTaskServiceImpl implements BpmTaskService {
FlowElement targetElement = validateTargetTaskCanReturn(task.getTaskDefinitionKey(),
reqVO.getTargetTaskDefinitionKey(), task.getProcessDefinitionId());
// 2. 调用 Flowable 框架的退逻辑
// 2. 调用 Flowable 框架的退逻辑
returnTask(task, targetElement, reqVO);
}
/**
* 退退
* 退退
*
* @param sourceKey Key
* @param targetKey key
@ -586,7 +691,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
throw exception(TASK_TARGET_NODE_NOT_EXISTS);
}
// 2.2 只有串行可到达的节点,才可以退。类似非串行、子流程无法退回
// 2.2 只有串行可到达的节点,才可以退。类似非串行、子流程无法退回
if (!BpmnModelUtils.isSequentialReachable(source, target, null)) {
throw exception(TASK_RETURN_FAIL_SOURCE_TARGET_ERROR);
}
@ -594,10 +699,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
}
/**
* 退
* 退
*
* @param currentTask 退
* @param targetElement 退
* @param currentTask 退
* @param targetElement 退
* @param reqVO
*/
public void returnTask(Task currentTask, FlowElement targetElement, BpmTaskReturnReqVO reqVO) {
@ -610,9 +715,9 @@ public class BpmTaskServiceImpl implements BpmTaskService {
List<UserTask> returnUserTaskList = BpmnModelUtils.iteratorFindChildUserTasks(targetElement, runTaskKeyList, null, null);
List<String> returnTaskKeyList = convertList(returnUserTaskList, UserTask::getId);
// 2. 给当前要被退的 task 数组,设置退意见
// 2. 给当前要被退的 task 数组,设置退意见
taskList.forEach(task -> {
// 需要排除掉,不需要设置退意见的任务
// 需要排除掉,不需要设置退意见的任务
if (!returnTaskKeyList.contains(task.getTaskDefinitionKey())) {
return;
}
@ -659,9 +764,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
taskService.setOwner(taskId, task.getAssignee());
// 3.2 执行委派,将任务委派给 delegateUser
taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString());
// 3.3 更新 task 状态。
// 为什么不更新原因?因为原因目前主要给审批通过、不通过时使用
updateTaskStatus(taskId, BpmTaskStatusEnum.DELEGATE.getStatus());
// 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中
}
@Override
@ -868,6 +971,11 @@ public class BpmTaskServiceImpl implements BpmTaskService {
handleParentTaskIfSign(task.getParentTaskId());
}
@Override
public void copyTask(Long userId, BpmTaskCopyReqVO reqVO) {
processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getReason(), reqVO.getId());
}
/**
*
*
@ -920,8 +1028,13 @@ public class BpmTaskServiceImpl implements BpmTaskService {
if (ObjectUtil.notEqual(transactionStatus, TransactionSynchronization.STATUS_COMMITTED)) {
return;
}
// TODO 芋艿:可以后续优化成 getSelf();
// 特殊情况一:【人工审核】审批人为空,根据配置是否要自动通过、自动拒绝
if (ObjectUtil.equal(approveType, BpmUserTaskApproveTypeEnum.USER.getType())) {
// 如果有审批人、或者拥有人,则说明不满足情况一,不自动通过、不自动拒绝
if (!ObjectUtil.isAllEmpty(task.getAssignee(), task.getOwner())) {
return;
}
if (ObjectUtil.equal(assignEmptyHandlerType, BpmUserTaskAssignEmptyHandlerTypeEnum.APPROVE.getType())) {
SpringUtil.getBean(BpmTaskService.class).approveTask(null, new BpmTaskApproveReqVO()
.setId(task.getId()).setReason(BpmReasonEnum.ASSIGN_EMPTY_APPROVE.getReason()));
@ -987,7 +1100,7 @@ public class BpmTaskServiceImpl implements BpmTaskService {
}
// 审批人与提交人为同一人时,根据 BpmUserTaskAssignStartUserHandlerTypeEnum 策略进行处理
if (StrUtil.equals(task.getAssignee(), processInstance.getStartUserId())) {
// 判断是否为退或者驳回:如果是退或者驳回不走这个策略
// 判断是否为退或者驳回:如果是退或者驳回不走这个策略
// TODO 芋艿:【优化】未来有没更好的判断方式?!另外,还要考虑清理机制。就是说,下次处理了之后,就移除这个标识
Boolean returnTaskFlag = runtimeService.getVariable(processInstance.getProcessInstanceId(),
String.format(PROCESS_INSTANCE_VARIABLE_RETURN_FLAG, task.getTaskDefinitionKey()), Boolean.class);

View File

@ -1,36 +0,0 @@
package cn.iocoder.yudao.module.bpm.service.task.bo;
import lombok.Data;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmApprovalDetailRespVO.ApprovalNodeInfo;
/**
* Response BO
*
* @author jason
*/
@Data
public class AlreadyRunApproveNodeRespBO {
/**
*
*/
private List<ApprovalNodeInfo> approveNodes;
/**
* ID ( Bpmn XML id)
*/
private Set<String> runNodeIds;
/**
* key: activityId, value:
* <p>
*
*/
private Map<String, ApprovalNodeInfo> runningApprovalNodes;
}

View File

@ -1,31 +1,41 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate;
import cn.hutool.core.map.MapUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.BpmTaskCandidateUserStrategy;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmUserTaskAssignStartUserHandlerTypeEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.other.BpmTaskCandidateAssignEmptyStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.user.BpmTaskCandidateUserStrategy;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmTaskCandidateStrategyEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.ExtensionElement;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Spy;
import org.mockito.internal.util.collections.Sets;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomString;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_NAMESPACE;
import static org.flowable.bpmn.constants.BpmnXMLConstants.FLOWABLE_EXTENSIONS_PREFIX;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.*;
/**
* {@link BpmTaskCandidateInvoker}
@ -39,44 +49,207 @@ public class BpmTaskCandidateInvokerTest extends BaseMockitoUnitTest {
@Mock
private AdminUserApi adminUserApi;
@Spy
private BpmTaskCandidateStrategy strategy ;
@Mock
private BpmProcessInstanceService processInstanceService;
@Spy
private List<BpmTaskCandidateStrategy> strategyList ;
private BpmTaskCandidateStrategy userStrategy;
@Mock
private BpmTaskCandidateAssignEmptyStrategy emptyStrategy;
@Spy
private List<BpmTaskCandidateStrategy> strategyList;
@BeforeEach
public void setUp() {
strategy = new BpmTaskCandidateUserStrategy(adminUserApi); // 创建strategy实例
strategyList = Collections.singletonList(strategy); // 创建strategyList
userStrategy = new BpmTaskCandidateUserStrategy(); // 创建 strategy 实例
when(emptyStrategy.getStrategy()).thenReturn(BpmTaskCandidateStrategyEnum.ASSIGN_EMPTY);
strategyList = List.of(userStrategy, emptyStrategy); // 创建 strategyList
taskCandidateInvoker = new BpmTaskCandidateInvoker(strategyList, adminUserApi);
}
/**
*
*/
@Test
public void testCalculateUsers() {
// 准备参数
String param = "1,2";
DelegateExecution execution = mock(DelegateExecution.class);
// mock 方法DelegateExecution
UserTask userTask = mock(UserTask.class);
when(execution.getCurrentFlowElement()).thenReturn(userTask);
when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)))
.thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString());
when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)))
.thenReturn(param);
// mock 方法adminUserApi
AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
.put(user2.getId(), user2).build();
when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
public void testCalculateUsersByTask_some() {
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
// 准备参数
String param = "1,2";
DelegateExecution execution = mock(DelegateExecution.class);
// mock 方法DelegateExecution
UserTask userTask = mock(UserTask.class);
String processInstanceId = randomString();
when(execution.getProcessInstanceId()).thenReturn(processInstanceId);
when(execution.getCurrentFlowElement()).thenReturn(userTask);
when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)))
.thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString());
when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)))
.thenReturn(param);
// mock 方法adminUserApi
AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
.put(user2.getId(), user2).build();
when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
// mock 移除发起人的用户
springUtilMockedStatic.when(() -> SpringUtil.getBean(BpmProcessInstanceService.class))
.thenReturn(processInstanceService);
ProcessInstance processInstance = mock(ProcessInstance.class);
when(processInstanceService.getProcessInstance(eq(processInstanceId))).thenReturn(processInstance);
when(processInstance.getStartUserId()).thenReturn("1");
mockFlowElementExtensionElement(userTask, BpmnModelConstants.USER_TASK_ASSIGN_START_USER_HANDLER_TYPE,
String.valueOf(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType()));
// 调用
Set<Long> results = taskCandidateInvoker.calculateUsers(execution);
// 断言
assertEquals(asSet(1L, 2L), results);
// 调用
Set<Long> results = taskCandidateInvoker.calculateUsersByTask(execution);
// 断言
assertEquals(asSet(2L), results);
}
}
/**
* empty
*/
@Test
public void testCalculateUsersByTask_none() {
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
// 准备参数
String param = "1,2";
DelegateExecution execution = mock(DelegateExecution.class);
// mock 方法DelegateExecution
UserTask userTask = mock(UserTask.class);
String processInstanceId = randomString();
when(execution.getProcessInstanceId()).thenReturn(processInstanceId);
when(execution.getCurrentFlowElement()).thenReturn(userTask);
when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)))
.thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy().toString());
when(userTask.getAttributeValue(eq(BpmnModelConstants.NAMESPACE), eq(BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)))
.thenReturn(param);
// mock 方法adminUserApi
AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
.put(user2.getId(), user2).build();
when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
// mock 方法empty
when(emptyStrategy.calculateUsersByTask(same(execution), same(param)))
.thenReturn(Sets.newSet(2L));
// mock 移除发起人的用户
springUtilMockedStatic.when(() -> SpringUtil.getBean(BpmProcessInstanceService.class))
.thenReturn(processInstanceService);
ProcessInstance processInstance = mock(ProcessInstance.class);
when(processInstanceService.getProcessInstance(eq(processInstanceId))).thenReturn(processInstance);
when(processInstance.getStartUserId()).thenReturn("1");
// 调用
Set<Long> results = taskCandidateInvoker.calculateUsersByTask(execution);
// 断言
assertEquals(asSet(2L), results);
}
}
/**
* empty
*/
@Test
public void testCalculateUsersByActivity_some() {
try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
// 准备参数
String param = "1,2";
BpmnModel bpmnModel = mock(BpmnModel.class);
String activityId = randomString();
Long startUserId = 1L;
String processDefinitionId = randomString();
Map<String, Object> processVariables = new HashMap<>();
// mock 方法DelegateExecution
UserTask userTask = mock(UserTask.class);
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateStrategy(same(userTask)))
.thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy());
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateParam(same(userTask)))
.thenReturn(param);
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))).thenReturn(userTask);
// mock 方法adminUserApi
AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
.put(user2.getId(), user2).build();
when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
// mock 移除发起人的用户
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseAssignStartUserHandlerType(same(userTask)))
.thenReturn(BpmUserTaskAssignStartUserHandlerTypeEnum.SKIP.getType());
// 调用
Set<Long> results = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,
startUserId, processDefinitionId, processVariables);
// 断言
assertEquals(asSet(2L), results);
}
}
/**
*
*/
@Test
public void testCalculateUsersByActivity_none() {
try (MockedStatic<BpmnModelUtils> bpmnModelUtilsMockedStatic = mockStatic(BpmnModelUtils.class)) {
// 准备参数
String param = "1,2";
BpmnModel bpmnModel = mock(BpmnModel.class);
String activityId = randomString();
Long startUserId = 1L;
String processDefinitionId = randomString();
Map<String, Object> processVariables = new HashMap<>();
// mock 方法DelegateExecution
UserTask userTask = mock(UserTask.class);
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateStrategy(same(userTask)))
.thenReturn(BpmTaskCandidateStrategyEnum.USER.getStrategy());
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.parseCandidateParam(same(userTask)))
.thenReturn(param);
bpmnModelUtilsMockedStatic.when(() -> BpmnModelUtils.getFlowElementById(same(bpmnModel), eq(activityId))).thenReturn(userTask);
// mock 方法adminUserApi
AdminUserRespDTO user1 = randomPojo(AdminUserRespDTO.class, o -> o.setId(1L)
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
AdminUserRespDTO user2 = randomPojo(AdminUserRespDTO.class, o -> o.setId(2L)
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
Map<Long, AdminUserRespDTO> userMap = MapUtil.builder(user1.getId(), user1)
.put(user2.getId(), user2).build();
when(adminUserApi.getUserMap(eq(asSet(1L, 2L)))).thenReturn(userMap);
// mock 方法empty
when(emptyStrategy.calculateUsersByActivity(same(bpmnModel), eq(activityId),
eq(param), same(startUserId), same(processDefinitionId), same(processVariables)))
.thenReturn(Sets.newSet(2L));
// 调用
Set<Long> results = taskCandidateInvoker.calculateUsersByActivity(bpmnModel, activityId,
startUserId, processDefinitionId, processVariables);
// 断言
assertEquals(asSet(2L), results);
}
}
private static void mockFlowElementExtensionElement(FlowElement element, String name, String value) {
if (value == null) {
return;
}
ExtensionElement extensionElement = new ExtensionElement();
extensionElement.setNamespace(FLOWABLE_EXTENSIONS_NAMESPACE);
extensionElement.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX);
extensionElement.setElementText(value);
extensionElement.setName(name);
// mock
Map<String, List<ExtensionElement>> extensionElements = element.getExtensionElements();
if (extensionElements == null) {
extensionElements = new LinkedHashMap<>();
}
extensionElements.put(name, Collections.singletonList(extensionElement));
when(element.getExtensionElements()).thenReturn(extensionElements);
}
@Test

View File

@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import org.assertj.core.util.Sets;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
public class BpmTaskCandidateDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateDeptLeaderMultiStrategy strategy;
@Mock
private DeptApi deptApi;
@Test
public void testCalculateUsers() {
// 准备参数
String param = "10,20|2";
// mock 方法
when(deptApi.getDept(any())).thenAnswer((Answer< CommonResult<DeptRespDTO>>) invocationOnMock -> {
Long deptId = invocationOnMock.getArgument(0);
return success(randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1)));
});
// 调用
Set<Long> userIds = strategy.calculateUsers(param);
// 断言结果
assertEquals(Sets.newLinkedHashSet(11L, 1001L, 21L, 2001L), userIds);
}
}

View File

@ -1,9 +1,10 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import org.junit.jupiter.api.Disabled;
import org.assertj.core.util.Sets;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
@ -11,14 +12,12 @@ import org.mockito.Mock;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@Disabled // TODO 芋艿:临时注释
public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
@ -30,16 +29,16 @@ public class BpmTaskCandidateDeptLeaderStrategyTest extends BaseMockitoUnitTest
@Test
public void testCalculateUsers() {
// 准备参数
String param = "1,2";
String param = "10,20";
// mock 方法
DeptRespDTO dept1 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(11L));
DeptRespDTO dept2 = randomPojo(DeptRespDTO.class, o -> o.setLeaderUserId(22L));
when(deptApi.getDeptList(eq(asSet(1L, 2L)))).thenReturn(success(asList(dept1, dept2)));
when(deptApi.getDeptList(eq(SetUtils.asSet(10L, 20L)))).thenReturn(success(asList(
randomPojo(DeptRespDTO.class, o -> o.setId(10L).setParentId(10L).setLeaderUserId(11L)),
randomPojo(DeptRespDTO.class, o -> o.setId(20L).setParentId(20L).setLeaderUserId(21L)))));
// 调用
Set<Long> results = strategy.calculateUsers(null, param);
// 断言
assertEquals(asSet(11L, 22L), results);
Set<Long> userIds = strategy.calculateUsers(param);
// 断言结果
assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds);
}
}

View File

@ -1,45 +1,47 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy;
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.junit.jupiter.api.Disabled;
import org.assertj.core.util.Sets;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import java.util.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
@Disabled // TODO 芋艿:临时注释
public class BpmTaskCandidateDeptMemberStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateDeptMemberStrategy strategy;
@Mock
private DeptApi deptApi;
@Mock
private AdminUserApi adminUserApi;
@Test
public void testCalculateUsers() {
// 准备参数
String param = "11,22";
String param = "10,20";
// mock 方法
List<AdminUserRespDTO> users = convertList(asSet(11L, 22L),
id -> new AdminUserRespDTO().setId(id));
when(adminUserApi.getUserListByDeptIds(eq(asSet(11L, 22L)))).thenReturn(success(users));
when(adminUserApi.getUserListByDeptIds(eq(SetUtils.asSet(10L, 20L)))).thenReturn(success(asList(
randomPojo(AdminUserRespDTO.class, o -> o.setId(11L)),
randomPojo(AdminUserRespDTO.class, o -> o.setId(21L)))));
// 调用
Set<Long> results = strategy.calculateUsers(null, param);
// 断言
assertEquals(asSet(11L, 22L), results);
Set<Long> userIds = strategy.calculateUsers(param);
// 断言结果
assertEquals(Sets.newLinkedHashSet(11L, 21L), userIds);
}
}

View File

@ -0,0 +1,84 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.strategy.dept;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import org.assertj.core.util.Sets;
import org.flowable.engine.delegate.DelegateExecution;
import org.flowable.engine.runtime.ProcessInstance;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.stubbing.Answer;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class BpmTaskCandidateStartUserDeptLeaderMultiStrategyTest extends BaseMockitoUnitTest {
@InjectMocks
private BpmTaskCandidateStartUserDeptLeaderMultiStrategy strategy;
@Mock
private BpmProcessInstanceService processInstanceService;
@Mock
private AdminUserApi adminUserApi;
@Mock
private DeptApi deptApi;
@Test
public void testCalculateUsersByTask() {
// 准备参数
String param = "2";
// mock 方法(获得流程发起人)
Long startUserId = 1L;
ProcessInstance processInstance = mock(ProcessInstance.class);
DelegateExecution execution = mock(DelegateExecution.class);
when(processInstanceService.getProcessInstance(eq(execution.getProcessInstanceId()))).thenReturn(processInstance);
when(processInstance.getStartUserId()).thenReturn(startUserId.toString());
// mock 方法(获取发起人的 multi 部门负责人)
mockGetStartUserDept(startUserId);
// 调用
Set<Long> userIds = strategy.calculateUsersByTask(execution, param);
// 断言
assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds);
}
@Test
public void testCalculateUsersByActivity() {
// 准备参数
String param = "2";
// mock 方法
Long startUserId = 1L;
mockGetStartUserDept(startUserId);
// 调用
Set<Long> userIds = strategy.calculateUsersByActivity(null, null, param,
startUserId, null, null);
// 断言
assertEquals(Sets.newLinkedHashSet(11L, 1001L), userIds);
}
private void mockGetStartUserDept(Long startUserId) {
when(adminUserApi.getUser(eq(startUserId))).thenReturn(
success(randomPojo(AdminUserRespDTO.class, o -> o.setId(startUserId).setDeptId(10L))));
when(deptApi.getDept(any())).thenAnswer((Answer< CommonResult<DeptRespDTO>>) invocationOnMock -> {
Long deptId = invocationOnMock.getArgument(0);
return success(randomPojo(DeptRespDTO.class, o -> o.setId(deptId).setParentId(deptId * 100).setLeaderUserId(deptId + 1)));
});
}
}

Some files were not shown because too many files have changed in this diff Show More