Compare commits

...

14 Commits

Author SHA1 Message Date
furongxin
dfeaa258a5 Merge branch 'dev' of http://git.znkjfw.com/ak/zn-cloud into frx 2025-06-30 16:49:07 +08:00
aikai
88313df8bd Merge branch '工单-dev' of http://git.znkjfw.com/ak/zn-cloud into dev
# Conflicts:
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/AttendanceController.java
2025-06-30 15:40:33 +08:00
aikai
fe23ccf41f docs:移除旧版文档和配置文件
- 删除了 Docker Compose 配置文件
- 移除了多个 UI 项目的 README 文件
- 删除了 HTTP 客户端环境配置文件
2025-06-30 15:37:08 +08:00
aikai
2eeb98a360 feat(bpm): 工单查询增加按级别筛选功能
- 在 BpmOAWorkOrderPageReqVO 中添加 level 字段,用于工单级别筛选
- 在 BpmOAWorkOrderMapper.xml 中增加对 level 的条件判断
- 优化 createTime 的判断逻辑,提高代码可读性
2025-06-30 14:16:19 +08:00
aikai
d057442422 feat(system): 添加地址逆解析功能
- 新增 AddressUtil 类实现逆地理编码功能
- 在 AttendanceController 中添加 addressInverseResolution 方法提供地址逆解析接口
- 使用天地图 API 进行经纬度到地址的转换
2025-06-30 11:44:21 +08:00
aikai
d79f7128f0 refactor(system): 移除地址逆解析相关代码
- 删除了 yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/address/AddressUtil.java 文件- 移除了 yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/attendance/AttendanceController.java 中的 addressInverseResolution 方法
2025-06-30 11:42:02 +08:00
aikai
1c6f687965 refactor(bpm): 重构工单跟踪记录相关功能
-移除 BpmOAWorkOrderTrackDO 中的操作类型字段
- 更新相关 VO 和 DTO,移除操作类型相关代码
- 修改工单创建和跟踪记录创建逻辑,将操作类型合并到内容中
- 新增地址逆解析相关功能
2025-06-30 11:22:45 +08:00
aikai
68d6971fda refactor(bpm): 修改 OA工单默认状态为处理中- 将 BpmOAWorkOrderServiceImpl 中工单默认状态从1(待分配)修改为2(处理中)
- 此修改确保新创建的工单直接进入处理流程,无需等待分配
2025-06-27 17:54:41 +08:00
aikai
113c68f824 feat(bpm): 优化流程定义列表过滤逻辑并添加工单流程结果字段
- 在 BpmProcessDefinitionController 中增加对 "work_order" 流程定义的过滤- 在 BpmOAWorkOrderUpdateReqVO 中添加 result 字段,用于 BPM 流程结果
- 在 BpmOAWorkOrderServiceImpl 中处理更新工单的 result 字段
2025-06-27 16:13:54 +08:00
aikai
513ae81160 feat(infra): 添加 MinIO预签名上传和下载功能- 新增生成预签名上传凭证和预签名下载 URL 的接口- 实现文件上传和下载的后端逻辑
- 添加 MinIO 相关配置项
-优化存储桶创建逻辑,支持多个存储桶
2025-06-27 14:44:55 +08:00
aikai
57da6321bf refactor(bpm): 移除 BpmOAWorkOrderTrackReqDTO 中 operationType 的 @NotEmpty 注解
移除了 BpmOAWorkOrderTrackReqDTO 类中 operationType 字段的 @NotEmpty 注解,保留其他原有逻辑。
2025-06-26 10:02:06 +08:00
aikai
d882806df6 feat(bpm): 新增工单分配规则功能并优化工单相关接口
- 新增工单分配规则相关实体、控制器、服务和映射
- 实现工单分配规则的创建、更新、删除和查询功能
- 优化工单分页查询接口,支持按责任人和部门筛选
- 新增工单跟踪记录分页查询功能
2025-06-24 17:58:13 +08:00
aikai
7ab86ea5cc feat(bpm): 新增工单模块设计和数据库结构
- 添加工单模块前端设计文档,详细描述了页面架构、权限设计和API调用流程
- 新增工单模块数据库表结构,包括工单主表、分配规则表和跟踪记录表
- 创建工单类型、状态和优先级的字典数据
- 添加示例分配规则数据
2025-06-21 09:15:01 +08:00
aikai
197605f4d2 feat(bpm): 新增工单模块设计和数据库结构
- 添加工单模块前端设计文档,详细描述了页面架构、权限设计和API调用流程
- 新增工单模块数据库表结构,包括工单主表、分配规则表和跟踪记录表
- 创建工单类型、状态和优先级的字典数据
- 添加示例分配规则数据
2025-06-20 16:00:50 +08:00
66 changed files with 3004 additions and 225 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,162 +0,0 @@
version: '3'
services:
yudao-gateway:
image: yudao-gateway
container_name: yudao-gateway
environment:
- TZ=Asia/Shanghai # 配置程序默认时区为上海(中国标准时间)
- JAVA_TOOL_OPTIONS=-javaagent:/data/skywalking/skywalking-agent/skywalking-agent.jar # 配置skywalking
- SW_AGENT_NAME=yudao-gateway
- SW_AGENT_TRACE_IGNORE_PATH=Redisson/PING,/actuator/**,/admin/**
- SW_AGENT_COLLECTOR_BACKEND_SERVICES=[YOUR_SKYWALKING_ADDR] # 请替换 your.skywalking.addr 为你的 skywalking 地址
- SPRING_PROFILES_ACTIVE=test # 指定程序运行环境
- SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=[YOUR_NACOS_ADDR] # 配置中心地址
- SPRING_CLOUD_NACOS_CONFIG_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
- SPRING_CLOUD_NACOS_SERVER_ADDR=[YOUR_NACOS_ADDR] # 注册中心地址
- SPRING_CLOUD_NACOS_DISCOVERY_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
volumes:
- /docker/yudao-cloud/logs:/root/logs/
- /data/skywalking/skywalking-agent:/data/skywalking/skywalking-agent
restart: always
network_mode: host # 以主机网络环境运行
yudao-system:
image: yudao-module-system-biz
container_name: yudao-system
environment:
- TZ=Asia/Shanghai # 配置程序默认时区为上海(中国标准时间)
- JAVA_TOOL_OPTIONS=-javaagent:/data/skywalking/skywalking-agent/skywalking-agent.jar # 配置skywalking
- SW_AGENT_NAME=yudao-gateway
- SW_AGENT_TRACE_IGNORE_PATH=Redisson/PING,/actuator/**,/admin/**
- SW_AGENT_COLLECTOR_BACKEND_SERVICES=[YOUR_SKYWALKING_ADDR] # 请替换 your.skywalking.addr 为你的 skywalking 地址
- SPRING_PROFILES_ACTIVE=test # 指定程序运行环境
- SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=[YOUR_NACOS_ADDR] # 配置中心地址
- SPRING_CLOUD_NACOS_CONFIG_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
- SPRING_CLOUD_NACOS_SERVER_ADDR=[YOUR_NACOS_ADDR] # 注册中心地址
- SPRING_CLOUD_NACOS_DISCOVERY_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
volumes:
- /docker/yudao-cloud/logs:/root/logs/
- /data/skywalking/skywalking-agent:/data/skywalking/skywalking-agent
healthcheck:
test: [ "CMD","curl","-f","http://localhost:48081" ]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
restart: always
network_mode: host
yudao-infra:
image: yudao-module-infra-biz
container_name: yudao-infra
environment:
- TZ=Asia/Shanghai # 配置程序默认时区为上海(中国标准时间)
- JAVA_TOOL_OPTIONS=-javaagent:/data/skywalking/skywalking-agent/skywalking-agent.jar # 配置skywalking
- SW_AGENT_NAME=yudao-gateway
- SW_AGENT_TRACE_IGNORE_PATH=Redisson/PING,/actuator/**,/admin/**
- SW_AGENT_COLLECTOR_BACKEND_SERVICES=[YOUR_SKYWALKING_ADDR] # 请替换 your.skywalking.addr 为你的 skywalking 地址
- SPRING_PROFILES_ACTIVE=test # 指定程序运行环境
- SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=[YOUR_NACOS_ADDR] # 配置中心地址
- SPRING_CLOUD_NACOS_CONFIG_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
- SPRING_CLOUD_NACOS_SERVER_ADDR=[YOUR_NACOS_ADDR] # 注册中心地址
- SPRING_CLOUD_NACOS_DISCOVERY_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
volumes:
- /docker/yudao-cloud/logs:/root/logs/
- /data/skywalking/skywalking-agent:/data/skywalking/skywalking-agent
restart: always
network_mode: host
healthcheck:
test: [ "CMD","curl","-f","http://localhost:48082" ]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
depends_on:
yudao-system:
condition: service_healthy
yudao-report:
image: yudao-module-report-biz
container_name: yudao-report
environment:
- TZ=Asia/Shanghai # 配置程序默认时区为上海(中国标准时间)
- JAVA_TOOL_OPTIONS=-javaagent:/data/skywalking/skywalking-agent/skywalking-agent.jar # 配置skywalking
- SW_AGENT_NAME=yudao-gateway
- SW_AGENT_TRACE_IGNORE_PATH=Redisson/PING,/actuator/**,/admin/**
- SW_AGENT_COLLECTOR_BACKEND_SERVICES=[YOUR_SKYWALKING_ADDR] # 请替换 your.skywalking.addr 为你的 skywalking 地址
- SPRING_PROFILES_ACTIVE=test # 指定程序运行环境
- SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=[YOUR_NACOS_ADDR] # 配置中心地址
- SPRING_CLOUD_NACOS_CONFIG_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
- SPRING_CLOUD_NACOS_SERVER_ADDR=[YOUR_NACOS_ADDR] # 注册中心地址
- SPRING_CLOUD_NACOS_DISCOVERY_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
volumes:
- /docker/yudao-cloud/logs:/root/logs/
- /data/skywalking/skywalking-agent:/data/skywalking/skywalking-agent
restart: always
network_mode: host
depends_on:
yudao-infra:
condition: service_healthy
yudao-bpm:
image: yudao-module-bpm-biz
container_name: yudao-bpm
environment:
- TZ=Asia/Shanghai # 配置程序默认时区为上海(中国标准时间)
- JAVA_TOOL_OPTIONS=-javaagent:/data/skywalking/skywalking-agent/skywalking-agent.jar # 配置skywalking
- SW_AGENT_NAME=yudao-gateway
- SW_AGENT_TRACE_IGNORE_PATH=Redisson/PING,/actuator/**,/admin/**
- SW_AGENT_COLLECTOR_BACKEND_SERVICES=[YOUR_SKYWALKING_ADDR] # 请替换 your.skywalking.addr 为你的 skywalking 地址
- SPRING_PROFILES_ACTIVE=test # 指定程序运行环境
- SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=[YOUR_NACOS_ADDR] # 配置中心地址
- SPRING_CLOUD_NACOS_CONFIG_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
- SPRING_CLOUD_NACOS_SERVER_ADDR=[YOUR_NACOS_ADDR] # 注册中心地址
- SPRING_CLOUD_NACOS_DISCOVERY_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
volumes:
- /docker/yudao-cloud/logs:/root/logs/
- /data/skywalking/skywalking-agent:/data/skywalking/skywalking-agent
restart: always
network_mode: host
depends_on:
yudao-infra:
condition: service_healthy
yudao-pay:
image: yudao-module-pay-biz
container_name: yudao-pay
environment:
- TZ=Asia/Shanghai # 配置程序默认时区为上海(中国标准时间)
- JAVA_TOOL_OPTIONS=-javaagent:/data/skywalking/skywalking-agent/skywalking-agent.jar # 配置skywalking
- SW_AGENT_NAME=yudao-gateway
- SW_AGENT_TRACE_IGNORE_PATH=Redisson/PING,/actuator/**,/admin/**
- SW_AGENT_COLLECTOR_BACKEND_SERVICES=[YOUR_SKYWALKING_ADDR] # 请替换 your.skywalking.addr 为你的 skywalking 地址
- SPRING_PROFILES_ACTIVE=test # 指定程序运行环境
- SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=[YOUR_NACOS_ADDR] # 配置中心地址
- SPRING_CLOUD_NACOS_CONFIG_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
- SPRING_CLOUD_NACOS_SERVER_ADDR=[YOUR_NACOS_ADDR] # 注册中心地址
- SPRING_CLOUD_NACOS_DISCOVERY_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
volumes:
- /docker/yudao-cloud/logs:/root/logs/
- /data/skywalking/skywalking-agent:/data/skywalking/skywalking-agent
restart: always
network_mode: host
depends_on:
yudao-infra:
condition: service_healthy
yudao-mp:
image: yudao-module-mp-biz
container_name: yudao-mp
environment:
- TZ=Asia/Shanghai # 配置程序默认时区为上海(中国标准时间)
- JAVA_TOOL_OPTIONS=-javaagent:/data/skywalking/skywalking-agent/skywalking-agent.jar # 配置skywalking
- SW_AGENT_NAME=yudao-gateway
- SW_AGENT_TRACE_IGNORE_PATH=Redisson/PING,/actuator/**,/admin/**
- SW_AGENT_COLLECTOR_BACKEND_SERVICES=[YOUR_SKYWALKING_ADDR] # 请替换 your.skywalking.addr 为你的 skywalking 地址
- SPRING_PROFILES_ACTIVE=test # 指定程序运行环境
- SPRING_CLOUD_NACOS_CONFIG_SERVER_ADDR=[YOUR_NACOS_ADDR] # 配置中心地址
- SPRING_CLOUD_NACOS_CONFIG_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
- SPRING_CLOUD_NACOS_SERVER_ADDR=[YOUR_NACOS_ADDR] # 注册中心地址
- SPRING_CLOUD_NACOS_DISCOVERY_NAMESPACE=[YOUR_NAMESPACE] # 命名空间
volumes:
- /docker/yudao-cloud/logs:/root/logs/
- /data/skywalking/skywalking-agent:/data/skywalking/skywalking-agent
restart: always
network_mode: host
depends_on:
yudao-infra:
condition: service_healthy

View File

@ -1,28 +0,0 @@
{
"local": {
"baseUrl": "http://127.0.0.1:48080/admin-api",
"systemBaseUrl": "http://127.0.0.1:48081/admin-api",
"infaBaseUrl": "http://127.0.0.1:48082/admin-api",
"token": "test1",
"adminTenentId": "1",
"tag": "${HOSTNAME}",
"appApi": "http://127.0.0.1:48080/app-api",
"appToken": "test1",
"appTenentId": "1"
},
"gateway": {
"baseUrl": "http://127.0.0.1:48080/admin-api",
"systemBaseUrl": "http://127.0.0.1:48080/admin-api",
"infaBaseUrl": "http://127.0.0.1:48080/admin-api",
"token": "test1",
"adminTenentId": "1",
"tag": "${HOSTNAME}",
"appApi": "http://127.0.0.1:8888/app-api",
"appToken": "test1",
"appTenentId": "1"
}
}

BIN
sql/.DS_Store vendored

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,82 @@
package cn.iocoder.yudao.framework.common.util.address;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
public class AddressUtil {
// 替换成您在天地图官网申请的密钥
private static final String API_KEY = "e11694af0e347f991a0b18f10f2491d3";
public static void main(String[] args) {
// 示例经纬度深圳腾讯大厦
double longitude = 115.43835;
double latitude = 28.200279;
// 35.88052,114.8731
try {
String result = reverseGeocode(longitude, latitude);
System.out.println("逆地理编码结果:");
System.out.println(result);
} catch (Exception e) {
System.err.println("逆地理编码失败:");
e.printStackTrace();
}
}
public static String reverseGeocode(double longitude, double latitude) throws Exception {
// 1. 构建请求参数注意使用双引号
String postStr = String.format("{\"lon\":%f,\"lat\":%f,\"ver\":2}", longitude, latitude);
// 2. URL编码参数
String encodedPostStr = URLEncoder.encode(postStr, StandardCharsets.UTF_8.name());
// 3. 构建完整URL
String urlStr = "http://api.tianditu.gov.cn/geocoder?postStr=" + encodedPostStr
+ "&type=geocode&tk=" + API_KEY;
// 4. 创建HTTP连接
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
// 5. 设置请求头模拟浏览器访问
conn.setRequestProperty("User-Agent", "Mozilla/5.0");
conn.setRequestProperty("Accept", "application/json");
// 6. 获取响应
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 读取成功响应
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
return response.toString();
}
} else {
// 读取错误信息
try (BufferedReader errorReader = new BufferedReader(
new InputStreamReader(conn.getErrorStream(), StandardCharsets.UTF_8))) {
StringBuilder errorResponse = new StringBuilder();
String line;
while ((line = errorReader.readLine()) != null) {
errorResponse.append(line);
}
throw new RuntimeException("API请求失败: HTTP " + responseCode
+ "\n错误详情: " + errorResponse.toString());
}
}
}
}

View File

@ -124,5 +124,9 @@ public interface ErrorCodeConstants {
= new ErrorCode(1_009_012_002, "流程审批中或已通过当月不可重复申请");
ErrorCode THE_WORK_ORDER_RULE_TYPE_ALREADY_EXISTS_PLEASE_DO_NOT_ADD_IT_REPEATEDLY
= new ErrorCode(1_009_013_001, "工单规则类型已存在,请勿重复添加");
ErrorCode BPM_SYSTEM_BUG = new ErrorCode(1_009_012_001, "系统问题,请联系管理员");
}

View File

@ -25,7 +25,8 @@ public enum BpmTaskRuleScriptEnum {
LEADER_X8(27L, "调薪部门领导"),
LEADER_X9(28L, "调薪人上级领导"),
LEADER_X10(29L, "所选工厂的领导"),
LEADER_X11(30L, "审批人的上级领导");
LEADER_X11(30L, "审批人的上级领导"),
LEADER_X31(31L, "工单负责人");
/**
* 脚本编号

View File

@ -44,7 +44,7 @@ public class BpmProcessDefinitionController {
BpmProcessDefinitionListReqVO listReqVO) {
List<BpmProcessDefinitionRespVO> respVOS = bpmDefinitionService.getProcessDefinitionList(listReqVO);
respVOS = respVOS.stream().filter(data -> !data.getId().contains("work_task")).collect(Collectors.toList());
respVOS = respVOS.stream().filter(data -> !(data.getId().contains("work_task") || data.getId().contains("work_order"))).collect(Collectors.toList());
return success(respVOS);
}

View File

@ -0,0 +1,112 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule.BpmOAWorkOrderAssignRulePageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule.BpmOAWorkOrderAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule.BpmOAWorkOrderAssignRuleSaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderAssignRuleDO;
import cn.iocoder.yudao.module.bpm.service.oa.BpmOAWorkOrderAssignRuleService;
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.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.*;
/**
* 管理后台 - BPM OA 工单分配规则
*
* @author 系统
*/
@Tag(name = "管理后台 - BPM OA 工单分配规则")
@RestController
@RequestMapping("/bpm/work-order-assign-rule")
@Validated
public class BpmOAWorkOrderAssignRuleController {
@Resource
private BpmOAWorkOrderAssignRuleService workOrderAssignRuleService;
@PostMapping("/create")
@Operation(summary = "创建工单分配规则")
@PreAuthorize("@ss.hasPermission('bpm:work-order-assign-rule:create')")
@OperateLog(type = CREATE)
public CommonResult<Long> createWorkOrderAssignRule(@Valid @RequestBody BpmOAWorkOrderAssignRuleSaveReqVO createReqVO) {
return success(workOrderAssignRuleService.createWorkOrderAssignRule(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新工单分配规则")
@PreAuthorize("@ss.hasPermission('bpm:work-order-assign-rule:update')")
@OperateLog(type = UPDATE)
public CommonResult<Boolean> updateWorkOrderAssignRule(@Valid @RequestBody BpmOAWorkOrderAssignRuleSaveReqVO updateReqVO) {
workOrderAssignRuleService.updateWorkOrderAssignRule(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除工单分配规则")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('bpm:work-order-assign-rule:delete')")
@OperateLog(type = DELETE)
public CommonResult<Boolean> deleteWorkOrderAssignRule(@RequestParam("id") Long id) {
workOrderAssignRuleService.deleteWorkOrderAssignRule(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得工单分配规则")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('bpm:work-order-assign-rule:query')")
public CommonResult<BpmOAWorkOrderAssignRuleDO> getWorkOrderAssignRule(@RequestParam("id") Long id) {
BpmOAWorkOrderAssignRuleDO workOrderAssignRule = workOrderAssignRuleService.getWorkOrderAssignRule(id);
return success(workOrderAssignRule);
}
@GetMapping("/page")
@Operation(summary = "获得工单分配规则分页")
@PreAuthorize("@ss.hasPermission('bpm:work-order-assign-rule:query')")
public CommonResult<PageResult<BpmOAWorkOrderAssignRuleRespVO>> getWorkOrderAssignRulePage(@Valid BpmOAWorkOrderAssignRulePageReqVO pageReqVO) {
PageResult<BpmOAWorkOrderAssignRuleRespVO> pageResult = workOrderAssignRuleService.getWorkOrderAssignRulePage(pageReqVO);
return success(pageResult);
}
@GetMapping("/list")
@Operation(summary = "获得工单分配规则列表")
@PreAuthorize("@ss.hasPermission('bpm:work-order-assign-rule:query')")
public CommonResult<List<BpmOAWorkOrderAssignRuleRespVO>> getWorkOrderAssignRuleList(@Valid BpmOAWorkOrderAssignRulePageReqVO exportReqVO) {
List<BpmOAWorkOrderAssignRuleRespVO> list = workOrderAssignRuleService.getWorkOrderAssignRuleList(exportReqVO);
return success(list);
}
@GetMapping("/list-by-type")
@Operation(summary = "根据工单类型获取有效的分配规则")
@Parameter(name = "workOrderType", description = "工单类型", required = true)
@PreAuthorize("@ss.hasPermission('bpm:work-order-assign-rule:query')")
public CommonResult<List<BpmOAWorkOrderAssignRuleDO>> getEnabledRulesByType(@RequestParam("workOrderType") String workOrderType) {
List<BpmOAWorkOrderAssignRuleDO> list = workOrderAssignRuleService.getEnabledRulesByType(workOrderType);
return success(list);
}
@PutMapping("/update-status")
@Operation(summary = "批量更新规则状态")
@PreAuthorize("@ss.hasPermission('bpm:work-order-assign-rule:update')")
@OperateLog(type = UPDATE)
public CommonResult<Boolean> updateRuleStatus(@RequestParam("ids") Collection<Long> ids,
@RequestParam("status") Integer status) {
workOrderAssignRuleService.updateRuleStatus(ids, status);
return success(true);
}
}

View File

@ -0,0 +1,104 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder.*;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderDO;
import cn.iocoder.yudao.module.bpm.service.oa.BpmOAWorkOrderService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
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.annotation.security.PermitAll;
import javax.validation.Valid;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
/**
* 管理后台 - BPM OA 工单
*
* @author 系统
*/
@Tag(name = "管理后台 - BPM OA 工单")
@RestController
@RequestMapping("/bpm/oa/work-order")
@Validated
@Slf4j
public class BpmOAWorkOrderController {
@Resource
private BpmOAWorkOrderService workOrderService;
@PostMapping("/create")
@Operation(summary = "创建工单")
@PreAuthorize("@ss.hasPermission('bpm:oa-work-order:create')")
public CommonResult<Long> createWorkOrder(@Valid @RequestBody BpmOAWorkOrderCreateReqVO createReqVO) {
return success(workOrderService.createWorkOrder(getLoginUserId(), createReqVO));
}
@GetMapping("/page")
@Operation(summary = "获得工单分页")
@PreAuthorize("@ss.hasPermission('bpm:oa-work-order:query')")
public CommonResult<PageResult<BpmOAWorkOrderRespVO>> getWorkOrderPage(@Valid BpmOAWorkOrderPageReqVO pageReqVO) {
return success(workOrderService.getWorkOrderPage(getLoginUserId(), pageReqVO));
}
@GetMapping("/my-page")
@Operation(summary = "获得我发起的工单分页")
@PreAuthorize("@ss.hasPermission('bpm:oa-work-order:query')")
public CommonResult<PageResult<BpmOAWorkOrderRespVO>> getMyWorkOrderPage(@Valid BpmOAWorkOrderPageReqVO pageReqVO) {
return success(workOrderService.getMyWorkOrderPage(getLoginUserId(), pageReqVO));
}
@GetMapping("/assigned-page")
@Operation(summary = "获得分配给我的工单分页")
@PreAuthorize("@ss.hasPermission('bpm:oa-work-order:query')")
public CommonResult<PageResult<BpmOAWorkOrderRespVO>> getAssignedWorkOrderPage(@Valid BpmOAWorkOrderPageReqVO pageReqVO) {
return success(workOrderService.getAssignedWorkOrderPage(getLoginUserId(), pageReqVO));
}
@GetMapping("/get/{id}")
@Operation(summary = "获得工单详情")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('bpm:oa-work-order:query')")
public CommonResult<BpmOAWorkOrderRespVO> getWorkOrder(@PathVariable("id") Long id) {
return success(workOrderService.getWorkOrderDetail(id));
}
@PostMapping("/track")
@Operation(summary = "添加工单跟踪记录")
@PreAuthorize("@ss.hasPermission('bpm:oa-work-order:update')")
public CommonResult<Boolean> addTrackInfo(@Valid @RequestBody BpmOAWorkOrderTrackReqVO trackReqVO) {
workOrderService.addTrackInfo(getLoginUserId(), trackReqVO);
return success(true);
}
@GetMapping("/track-page")
@Operation(summary = "获得工单跟踪记录分页")
public CommonResult<PageResult<BpmOAWorkOrderTrackInfo>> getTrackPage(@Valid BpmOAWorkOrderTrackReqDTO dto) {
return success(workOrderService.getTrackPage(dto));
}
@PutMapping("/update")
@Operation(summary = "修改工单")
@PreAuthorize("@ss.hasPermission('bpm:oa-work-order:update')")
public CommonResult<Boolean> updateWorkOrder(@Valid @RequestBody BpmOAWorkOrderUpdateReqVO updateReqVO) {
workOrderService.updateWorkOrder(updateReqVO);
return success(true);
}
@GetMapping("/my-assigned-count")
@Operation(summary = "获得我的待处理工单数量")
@PermitAll
public CommonResult<Long> getMyAssignedWorkOrderCount() {
return success(workOrderService.getMyAssignedWorkOrderCount(getLoginUserId()));
}
}

View File

@ -0,0 +1,44 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder;
import cn.iocoder.yudao.framework.common.pojo.UploadUserFile;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - BPM OA 工单创建 Request VO")
@Data
public class BpmOAWorkOrderCreateReqVO {
@Schema(description = "工单标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "系统登录问题")
@NotEmpty(message = "工单标题不能为空")
private String title;
@Schema(description = "工单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "it_support")
@NotEmpty(message = "工单类型不能为空")
private String type;
@Schema(description = "优先级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "优先级别不能为空")
private Integer level;
@Schema(description = "工单内容描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "用户无法登录系统,提示密码错误")
@NotEmpty(message = "工单内容不能为空")
private String content;
@Schema(description = "期望完成时间", example = "2024-12-20 18:00:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime expectedTime;
@TableField(typeHandler = JacksonTypeHandler.class)
private List<UploadUserFile> fileItems;
}

View File

@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - BPM OA 工单分页查询 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class BpmOAWorkOrderPageReqVO extends PageParam {
@Schema(description = "工单标题", example = "系统登录问题")
private String title;
@Schema(description = "工单类型", example = "it_support")
private String type;
@Schema(description = "工单状态", example = "1")
private Integer status;
@Schema(description = "工单级别", example = "1")
private Integer level;
@Schema(description = "责任人ID", example = "1")
private Long assigneeUserId;
@Schema(description = "发起人ID", example = "1")
private Long fromUserId;
@Schema(description = "当前用户id", example = "1")
private Long loginUserId;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,92 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder;
import cn.iocoder.yudao.framework.common.pojo.UploadUserFile;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - BPM OA 工单 Response VO")
@Data
public class BpmOAWorkOrderRespVO {
@Schema(description = "工单ID", example = "1")
private Long id;
@Schema(description = "工单标题", example = "系统登录问题")
private String title;
@Schema(description = "工单类型", example = "it_support")
private String type;
@Schema(description = "工单类型名称", example = "IT支持")
private String typeName;
@Schema(description = "优先级别", example = "2")
private Integer level;
@Schema(description = "优先级名称", example = "")
private String levelName;
@Schema(description = "工单内容描述", example = "用户无法登录系统,提示密码错误")
private String content;
@Schema(description = "发起人ID", example = "1")
private Long fromUserId;
@Schema(description = "发起人姓名", example = "张三")
private String fromUserName;
@Schema(description = "发起部门ID", example = "100")
private Long fromDeptId;
@Schema(description = "发起部门名称", example = "技术部")
private String fromDeptName;
@Schema(description = "责任人ID", example = "2")
private Long assigneeUserId;
@Schema(description = "责任人姓名", example = "李四")
private String assigneeUserName;
@Schema(description = "责任部门ID", example = "101")
private Long assigneeDeptId;
@Schema(description = "责任部门名称", example = "运维部")
private String assigneeDeptName;
@Schema(description = "工单状态", example = "2")
private Integer status;
@Schema(description = "工单状态名称", example = "处理中")
private String statusName;
@Schema(description = "期望完成时间", example = "2024-12-20 18:00:00")
private LocalDateTime expectedTime;
@Schema(description = "实际完成时间", example = "2024-12-20 16:30:00")
private LocalDateTime completedTime;
@Schema(description = "工单处理结果说明", example = "已成功修复系统登录问题")
private String resultDescription;
@Schema(description = "BPM流程结果", example = "2")
private Integer result;
@Schema(description = "BPM流程实例ID", example = "proc_inst_123456")
private String processInstanceId;
@Schema(description = "附件文件列表")
private List<UploadUserFile> fileItems;
@Schema(description = "工单跟踪记录")
private List<BpmOAWorkOrderTrackInfo> trackInfo;
@Schema(description = "创建时间", example = "2024-12-19 10:00:00")
private LocalDateTime createTime;
@Schema(description = "更新时间", example = "2024-12-19 15:30:00")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* BPM OA 工单跟踪信息
*
* @author 系统
*/
@Schema(description = "BPM OA 工单跟踪信息")
@Data
public class BpmOAWorkOrderTrackInfo {
@Schema(description = "跟踪ID")
private String trackId;
@Schema(description = "操作人ID")
private Long operatorId;
@Schema(description = "操作人姓名")
private String operatorName;
@Schema(description = "操作内容")
private String content;
@Schema(description = "操作时间")
private String trackTime;
}

View File

@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Data
public class BpmOAWorkOrderTrackReqDTO extends PageParam {
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "工单ID不能为空")
private Long workOrderId;
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - BPM OA 工单跟踪记录 Request VO")
@Data
public class BpmOAWorkOrderTrackReqVO {
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "工单ID不能为空")
private Long workOrderId;
@Schema(description = "操作内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "已开始处理该工单,预计今天下午完成")
@NotEmpty(message = "操作内容不能为空")
private String content;
}

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - BPM OA 工单修改 Request VO")
@Data
public class BpmOAWorkOrderUpdateReqVO {
@Schema(description = "工单ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "工单ID不能为空")
private Long id;
@Schema(description = "工单状态", example = "3")
private Integer status;
@Schema(description = "BPM流程结果", example = "3")
private Integer result;
@Schema(description = "期望完成时间", example = "2024-12-20 18:00:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime expectedTime;
@Schema(description = "实际完成时间", example = "2024-12-20 16:30:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime completedTime;
@Schema(description = "工单处理结果说明", example = "已成功修复系统登录问题,用户可以正常登录")
private String resultDescription;
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - BPM OA 工单分配规则分页查询 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class BpmOAWorkOrderAssignRulePageReqVO extends PageParam {
@Schema(description = "工单类型", example = "it_support")
private String workOrderType;
@Schema(description = "责任部门ID", example = "100")
private Long deptId;
@Schema(description = "责任人ID", example = "1")
private Long assigneeUserId;
@Schema(description = "创建时间")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - BPM OA 工单分配规则 Response VO")
@Data
public class BpmOAWorkOrderAssignRuleRespVO {
@Schema(description = "规则ID主键", example = "1")
private Long id;
@Schema(description = "工单类型", example = "it_support")
private String workOrderType;
@Schema(description = "工单类型名称", example = "IT支持")
private String workOrderTypeName;
@Schema(description = "责任部门ID", example = "100")
private Long deptId;
@Schema(description = "责任部门名称", example = "运维部")
private String deptName;
@Schema(description = "责任人ID", example = "1")
private Long assigneeUserId;
@Schema(description = "责任人姓名", example = "张三")
private String assigneeUserName;
@Schema(description = "规则描述", example = "IT支持类工单自动分配给运维部门张三处理")
private String description;
@Schema(description = "创建时间", example = "2024-01-01 10:00:00")
private LocalDateTime createTime;
@Schema(description = "更新时间", example = "2024-01-01 10:00:00")
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - BPM OA 工单分配规则新增/修改 Request VO")
@Data
public class BpmOAWorkOrderAssignRuleSaveReqVO {
@Schema(description = "规则ID主键", example = "1")
private Long id;
@Schema(description = "工单类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "it_support")
@NotEmpty(message = "工单类型不能为空")
private String workOrderType;
@Schema(description = "责任部门ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "责任部门ID不能为空")
private Long deptId;
@Schema(description = "责任人ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "责任人ID不能为空")
private Long assigneeUserId;
@Schema(description = "规则描述", example = "IT支持类工单自动分配给运维部门张三处理")
private String description;
}

View File

@ -0,0 +1,49 @@
package cn.iocoder.yudao.module.bpm.dal.dataobject.oa;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* BPM OA 工单分配规则 DO
*
* @author 系统
*/
@TableName("bpm_oa_work_order_assign_rule")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BpmOAWorkOrderAssignRuleDO extends BaseDO {
/**
* 规则ID主键
*/
@TableId
private Long id;
/**
* 工单类型
* 字典值work_order_type
*/
private String workOrderType;
/**
* 责任部门ID
*/
private Long deptId;
/**
* 责任人ID
*/
private Long assigneeUserId;
/**
* 规则描述
*/
private String description;
}

View File

@ -0,0 +1,125 @@
package cn.iocoder.yudao.module.bpm.dal.dataobject.oa;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder.BpmOAWorkOrderTrackInfo;
import cn.iocoder.yudao.framework.common.pojo.UploadUserFile;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* BPM OA 工单 DO
*
* @author 系统
*/
@TableName(value = "bpm_oa_work_order", autoResultMap = true)
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BpmOAWorkOrderDO extends BaseDO {
/**
* 工单ID主键
*/
@TableId
private Long id;
/**
* 工单标题
*/
private String title;
/**
* 工单类型
* 字典值work_order_type
*/
private String type;
/**
* 优先级别
* 1-2-3-4-紧急
*/
private Integer level;
/**
* 工单内容描述
*/
private String content;
/**
* 发起人ID
*/
private Long fromUserId;
/**
* 发起人部门ID
*/
private Long fromDeptId;
/**
* 责任人ID分配给谁处理
*/
private Long assigneeUserId;
/**
* 责任部门ID
*/
private Long assigneeDeptId;
/**
* 工单状态
* 1-待分配2-处理中3-已完成4-已取消5-已关闭
*/
private Integer status;
/**
* 期望完成时间
*/
private LocalDateTime expectedTime;
/**
* 实际完成时间
*/
private LocalDateTime completedTime;
/**
* 工单处理结果说明
*/
private String resultDescription;
/**
* BPM流程结果
*
* 枚举 {@link BpmProcessInstanceResultEnum}
*/
private Integer result;
/**
* 对应的BPM流程实例ID
*
* 关联 ProcessInstance id 属性
*/
private String processInstanceId;
/**
* 附件信息
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<UploadUserFile> fileItems;
/**
* 工单跟踪记录信息
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<BpmOAWorkOrderTrackInfo> trackInfo;
}

View File

@ -0,0 +1,58 @@
package cn.iocoder.yudao.module.bpm.dal.dataobject.oa;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import cn.iocoder.yudao.framework.mybatis.core.type.JsonLongSetTypeHandler;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
/**
* BPM OA 工单跟踪记录 DO
*
* @author 系统
*/
@TableName(value = "bpm_oa_work_order_track", autoResultMap = true)
@KeySequence("bpm_oa_work_order_track_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class BpmOAWorkOrderTrackDO extends BaseDO {
/**
* 跟踪记录ID
*/
@TableId
private Long id;
/**
* 工单ID
*/
private Long workOrderId;
/**
* 操作人ID
*/
private Long operatorId;
/**
* 操作人姓名
*/
private String operatorName;
/**
* 操作内容/备注
*/
private String content;
/**
* 附件文件列表JSON格式
*/
@TableField(typeHandler = JsonLongSetTypeHandler.class)
private String attachments;
}

View File

@ -0,0 +1,55 @@
package cn.iocoder.yudao.module.bpm.dal.mysql.oa;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule.BpmOAWorkOrderAssignRulePageReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderAssignRuleDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* BPM OA 工单分配规则 Mapper
*
* @author 系统
*/
@Mapper
public interface BpmOAWorkOrderAssignRuleMapper extends BaseMapperX<BpmOAWorkOrderAssignRuleDO> {
/**
* 根据工单类型查询启用的分配规则
*/
default List<BpmOAWorkOrderAssignRuleDO> selectRulesByType(String workOrderType) {
return selectList(new LambdaQueryWrapperX<BpmOAWorkOrderAssignRuleDO>()
.eq(BpmOAWorkOrderAssignRuleDO::getWorkOrderType, workOrderType));
}
/**
* 根据部门ID查询分配规则
*/
default List<BpmOAWorkOrderAssignRuleDO> selectRulesByDept(Long deptId) {
return selectList(new LambdaQueryWrapperX<BpmOAWorkOrderAssignRuleDO>()
.eq(BpmOAWorkOrderAssignRuleDO::getDeptId, deptId));
}
/**
* 根据责任人ID查询分配规则
*/
default List<BpmOAWorkOrderAssignRuleDO> selectRulesByAssignee(Long assigneeUserId) {
return selectList(new LambdaQueryWrapperX<BpmOAWorkOrderAssignRuleDO>()
.eq(BpmOAWorkOrderAssignRuleDO::getAssigneeUserId, assigneeUserId));
}
/**
* 分页查询工单分配规则
*/
default PageResult<BpmOAWorkOrderAssignRuleDO> selectPage(BpmOAWorkOrderAssignRulePageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<BpmOAWorkOrderAssignRuleDO>()
.likeIfPresent(BpmOAWorkOrderAssignRuleDO::getWorkOrderType, reqVO.getWorkOrderType())
.eqIfPresent(BpmOAWorkOrderAssignRuleDO::getDeptId, reqVO.getDeptId())
.eqIfPresent(BpmOAWorkOrderAssignRuleDO::getAssigneeUserId, reqVO.getAssigneeUserId())
.betweenIfPresent(BpmOAWorkOrderAssignRuleDO::getCreateTime, reqVO.getCreateTime()));
}
}

View File

@ -0,0 +1,90 @@
package cn.iocoder.yudao.module.bpm.dal.mysql.oa;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder.BpmOAWorkOrderPageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder.BpmOAWorkOrderRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderDO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* BPM OA 工单 Mapper
*
* @author 系统
*/
@Mapper
public interface BpmOAWorkOrderMapper extends BaseMapperX<BpmOAWorkOrderDO> {
/**
* 工单分页查询
*/
default PageResult<BpmOAWorkOrderDO> selectPage(Long loginUserId, BpmOAWorkOrderPageReqVO pageVO) {
return selectPage(pageVO, new LambdaQueryWrapperX<BpmOAWorkOrderDO>()
.likeIfPresent(BpmOAWorkOrderDO::getTitle, pageVO.getTitle())
.eqIfPresent(BpmOAWorkOrderDO::getType, pageVO.getType())
.eqIfPresent(BpmOAWorkOrderDO::getStatus, pageVO.getStatus())
.eqIfPresent(BpmOAWorkOrderDO::getAssigneeUserId, pageVO.getAssigneeUserId())
.eqIfPresent(BpmOAWorkOrderDO::getFromUserId, pageVO.getFromUserId())
.betweenIfPresent(BpmOAWorkOrderDO::getCreateTime, pageVO.getCreateTime())
.orderByDesc(BpmOAWorkOrderDO::getId));
}
/**
* 查询我的工单分页我发起的
*/
default PageResult<BpmOAWorkOrderDO> selectMyPage(Long loginUserId, BpmOAWorkOrderPageReqVO pageVO) {
return selectPage(pageVO, new LambdaQueryWrapperX<BpmOAWorkOrderDO>()
.eq(BpmOAWorkOrderDO::getFromUserId, loginUserId)
.likeIfPresent(BpmOAWorkOrderDO::getTitle, pageVO.getTitle())
.eqIfPresent(BpmOAWorkOrderDO::getType, pageVO.getType())
.eqIfPresent(BpmOAWorkOrderDO::getStatus, pageVO.getStatus())
.betweenIfPresent(BpmOAWorkOrderDO::getCreateTime, pageVO.getCreateTime())
.orderByDesc(BpmOAWorkOrderDO::getId));
}
/**
* 查询我负责的工单分页分配给我的
*/
default PageResult<BpmOAWorkOrderDO> selectAssignedPage(Long loginUserId, BpmOAWorkOrderPageReqVO pageVO) {
return selectPage(pageVO, new LambdaQueryWrapperX<BpmOAWorkOrderDO>()
.eq(BpmOAWorkOrderDO::getAssigneeUserId, loginUserId)
.likeIfPresent(BpmOAWorkOrderDO::getTitle, pageVO.getTitle())
.eqIfPresent(BpmOAWorkOrderDO::getType, pageVO.getType())
.eqIfPresent(BpmOAWorkOrderDO::getStatus, pageVO.getStatus())
.betweenIfPresent(BpmOAWorkOrderDO::getCreateTime, pageVO.getCreateTime())
.orderByDesc(BpmOAWorkOrderDO::getId));
}
/**
* 查询我负责工单的数量
*/
default Long selectMyAssignedCount(Long userId) {
return selectCount(new LambdaQueryWrapperX<BpmOAWorkOrderDO>()
.eq(BpmOAWorkOrderDO::getAssigneeUserId, userId)
.in(BpmOAWorkOrderDO::getStatus, 1, 2)); // 待分配和处理中
}
/**
* 工单分页查询带关联信息- XML实现
*/
IPage<BpmOAWorkOrderRespVO> selectWorkOrderPage(@Param("page") Page page,
@Param("req") BpmOAWorkOrderPageReqVO req);
/**
* 我发起的工单分页查询带关联信息- XML实现
*/
PageResult<BpmOAWorkOrderRespVO> selectMyWorkOrderPage(@Param("page") Page page,
@Param("req") BpmOAWorkOrderPageReqVO req);
/**
* 分配给我的工单分页查询带关联信息- XML实现
*/
IPage<BpmOAWorkOrderRespVO> selectAssignedWorkOrderPage(@Param("page") Page page,
@Param("req") BpmOAWorkOrderPageReqVO req);
BpmOAWorkOrderDO selectByProcessInstanceId(@Param("processInstanceId") String processInstanceId);
}

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.bpm.dal.mysql.oa;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder.BpmOAWorkOrderTrackInfo;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder.BpmOAWorkOrderTrackReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderTrackDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* BPM OA 工单跟踪记录 Mapper
*
* @author 系统
*/
@Mapper
public interface BpmOAWorkOrderTrackMapper extends BaseMapperX<BpmOAWorkOrderTrackDO> {
/**
* 根据工单ID查询跟踪记录列表
*/
default List<BpmOAWorkOrderTrackDO> selectByWorkOrderId(Long workOrderId) {
return selectList(new LambdaQueryWrapperX<BpmOAWorkOrderTrackDO>()
.eq(BpmOAWorkOrderTrackDO::getWorkOrderId, workOrderId)
.orderByAsc(BpmOAWorkOrderTrackDO::getCreateTime));
}
}

View File

@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.bpm.enums.oa;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 工单优先级枚举
*
* @author 系统
*/
@Getter
@AllArgsConstructor
public enum WorkOrderLevelEnum {
LOW(1, ""),
MEDIUM(2, ""),
HIGH(3, ""),
URGENT(4, "紧急");
/**
* 级别值
*/
private final Integer level;
/**
* 级别名称
*/
private final String name;
/**
* 根据级别值获取枚举
*/
public static WorkOrderLevelEnum getByLevel(Integer level) {
for (WorkOrderLevelEnum levelEnum : values()) {
if (levelEnum.getLevel().equals(level)) {
return levelEnum;
}
}
return null;
}
/**
* 判断是否为紧急级别
*/
public static boolean isUrgent(Integer level) {
return URGENT.getLevel().equals(level);
}
/**
* 判断是否为高优先级高或紧急
*/
public static boolean isHighPriority(Integer level) {
return HIGH.getLevel().equals(level) || URGENT.getLevel().equals(level);
}
}

View File

@ -0,0 +1,64 @@
package cn.iocoder.yudao.module.bpm.enums.oa;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 工单状态枚举
*
* @author 系统
*/
@Getter
@AllArgsConstructor
public enum WorkOrderStatusEnum {
PENDING_ASSIGN(1, "待分配"),
PROCESSING(2, "处理中"),
COMPLETED(3, "已完成"),
CANCELLED(4, "已取消"),
CLOSED(5, "已关闭");
/**
* 状态值
*/
private final Integer status;
/**
* 状态名称
*/
private final String name;
/**
* 根据状态值获取枚举
*/
public static WorkOrderStatusEnum valueOf(Integer status) {
for (WorkOrderStatusEnum statusEnum : values()) {
if (statusEnum.getStatus().equals(status)) {
return statusEnum;
}
}
return null;
}
/**
* 判断是否为处理中状态
*/
public static boolean isProcessing(Integer status) {
return PROCESSING.getStatus().equals(status);
}
/**
* 判断是否为已完成状态
*/
public static boolean isCompleted(Integer status) {
return COMPLETED.getStatus().equals(status);
}
/**
* 判断是否为待分配状态
*/
public static boolean isPendingAssign(Integer status) {
return PENDING_ASSIGN.getStatus().equals(status);
}
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.bpm.enums.oa;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 工单跟踪记录操作类型枚举
*
* @author 系统
*/
@AllArgsConstructor
@Getter
public enum WorkOrderTrackTypeEnum {
CREATE("CREATE", "创建工单"),
ASSIGN("ASSIGN", "分配工单"),
PROCESS("PROCESS", "处理中"),
COMPLETE("COMPLETE", "完成工单"),
CANCEL("CANCEL", "取消工单"),
COMMENT("COMMENT", "添加备注"),
TRANSFER("TRANSFER", "转派工单"),
REOPEN("REOPEN", "重新打开"),
CLOSE("CLOSE", "关闭工单");
/**
* 操作类型
*/
private final String type;
/**
* 操作描述
*/
private final String description;
/**
* 根据类型获取枚举
*/
public static WorkOrderTrackTypeEnum getByType(String type) {
for (WorkOrderTrackTypeEnum trackType : values()) {
if (trackType.getType().equals(type)) {
return trackType;
}
}
return null;
}
}

View File

@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.bpm.enums.oa;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 工单类型枚举
*
* @author 系统
*/
@Getter
@AllArgsConstructor
public enum WorkOrderTypeEnum {
IT_SUPPORT("it_support", "IT支持"),
EQUIPMENT_REPAIR("equipment_repair", "设备维修"),
SYSTEM_ISSUE("system_issue", "系统问题"),
PERMISSION_REQUEST("permission_request", "权限申请"),
OTHER("other", "其他");
/**
* 类型值
*/
private final String type;
/**
* 类型名称
*/
private final String name;
/**
* 根据类型值获取枚举
*/
public static WorkOrderTypeEnum getByType(String type) {
for (WorkOrderTypeEnum typeEnum : values()) {
if (typeEnum.getType().equals(type)) {
return typeEnum;
}
}
return null;
}
}

View File

@ -0,0 +1,61 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.impl;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task.BpmTaskRespVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAShiftjobsDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkTaskDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAShiftjobsMapper;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAWorkOrderMapper;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskRuleScriptEnum;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.behavior.script.BpmTaskAssignScript;
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;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
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.List;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
import static java.util.Collections.emptySet;
@Component
public class BpmOaWorkOrderLeaderScript implements BpmTaskAssignScript {
@Resource
private DeptApi deptApi;
@Resource
private BpmOAWorkOrderMapper bpmOAWorkOrderMapper;
@Resource
@Lazy // 解决循环依赖
private BpmProcessInstanceService bpmProcessInstanceService;
@Resource
@Lazy // 解决循环依赖
private BpmTaskService bpmTaskService ;
@Override
public Set<Long> calculateTaskCandidateUsers(DelegateExecution execution) {
// 获得发起人
ProcessInstance processInstance = bpmProcessInstanceService.getProcessInstance(execution.getProcessInstanceId());
List<BpmTaskRespVO> bpmTaskRespVOs = bpmTaskService.getTaskListByProcessInstanceId(processInstance.getProcessInstanceId());
if (CollUtil.isEmpty(bpmTaskRespVOs)) {
return emptySet();
}
//根据流程实例ID 取到调岗流程表单
BpmOAWorkOrderDO bpmOAWorkOrderDO = bpmOAWorkOrderMapper.selectByProcessInstanceId(processInstance.getProcessInstanceId());
//获取调岗部门ID
return bpmOAWorkOrderDO.getAssigneeUserId() != null ? asSet(bpmOAWorkOrderDO.getAssigneeUserId()) : emptySet();
}
@Override
public BpmTaskRuleScriptEnum getEnum() {
return BpmTaskRuleScriptEnum.LEADER_X31;
}
}

View File

@ -0,0 +1,82 @@
package cn.iocoder.yudao.module.bpm.service.oa;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule.BpmOAWorkOrderAssignRulePageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule.BpmOAWorkOrderAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule.BpmOAWorkOrderAssignRuleSaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderAssignRuleDO;
import javax.validation.Valid;
import java.util.Collection;
import java.util.List;
/**
* BPM OA 工单分配规则 Service 接口
*
* @author 系统
*/
public interface BpmOAWorkOrderAssignRuleService {
/**
* 创建工单分配规则
*
* @param createReqVO 创建信息
* @return 编号
*/
Long createWorkOrderAssignRule(@Valid BpmOAWorkOrderAssignRuleSaveReqVO createReqVO);
/**
* 更新工单分配规则
*
* @param updateReqVO 更新信息
*/
void updateWorkOrderAssignRule(@Valid BpmOAWorkOrderAssignRuleSaveReqVO updateReqVO);
/**
* 删除工单分配规则
*
* @param id 编号
*/
void deleteWorkOrderAssignRule(Long id);
/**
* 获得工单分配规则
*
* @param id 编号
* @return 工单分配规则
*/
BpmOAWorkOrderAssignRuleDO getWorkOrderAssignRule(Long id);
/**
* 获得工单分配规则分页
*
* @param pageReqVO 分页查询
* @return 工单分配规则分页
*/
PageResult<BpmOAWorkOrderAssignRuleRespVO> getWorkOrderAssignRulePage(BpmOAWorkOrderAssignRulePageReqVO pageReqVO);
/**
* 获得工单分配规则列表用于导出
*
* @param exportReqVO 查询条件
* @return 工单分配规则列表
*/
List<BpmOAWorkOrderAssignRuleRespVO> getWorkOrderAssignRuleList(BpmOAWorkOrderAssignRulePageReqVO exportReqVO);
/**
* 根据工单类型获取有效的分配规则
*
* @param workOrderType 工单类型
* @return 分配规则列表
*/
List<BpmOAWorkOrderAssignRuleDO> getEnabledRulesByType(String workOrderType);
/**
* 批量更新规则状态
*
* @param ids 规则ID列表
* @param status 状态1-启用0-禁用
*/
void updateRuleStatus(Collection<Long> ids, Integer status);
}

View File

@ -0,0 +1,183 @@
package cn.iocoder.yudao.module.bpm.service.oa;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule.BpmOAWorkOrderAssignRulePageReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule.BpmOAWorkOrderAssignRuleRespVO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorderassignrule.BpmOAWorkOrderAssignRuleSaveReqVO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderAssignRuleDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAWorkOrderAssignRuleMapper;
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.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
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.List;
import java.util.Map;
import java.util.stream.Collectors;
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.OA_WORK_TASK_NOT_EXISTS;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.THE_WORK_ORDER_RULE_TYPE_ALREADY_EXISTS_PLEASE_DO_NOT_ADD_IT_REPEATEDLY;
/**
* BPM OA 工单分配规则 Service 实现类
*
* @author 系统
*/
@Service
@Validated
public class BpmOAWorkOrderAssignRuleServiceImpl implements BpmOAWorkOrderAssignRuleService {
@Resource
private BpmOAWorkOrderAssignRuleMapper workOrderAssignRuleMapper;
@Resource
private AdminUserApi userApi;
@Resource
private DeptApi deptApi;
@Resource
private DictDataApi dictDataApi;
private static final String WORK_ORDER_TYPE = "work_order_type";
private static final String WORK_ORDER_ASSIGN_RULE_STATUS = "work_order_assign_rule_status";
@Override
@Transactional(rollbackFor = Exception.class)
public Long createWorkOrderAssignRule(BpmOAWorkOrderAssignRuleSaveReqVO createReqVO) {
// 插入
BpmOAWorkOrderAssignRuleDO workOrderAssignRule = BeanUtils.toBean(createReqVO, BpmOAWorkOrderAssignRuleDO.class);
// -- 判断系统中是否已经存在该规则 - 如果存在提示已存在 - 请勿重复添加
BpmOAWorkOrderAssignRuleDO bpmOAWorkOrderAssignRuleDO = workOrderAssignRuleMapper.selectOne(new LambdaQueryWrapperX<BpmOAWorkOrderAssignRuleDO>()
.eq(BpmOAWorkOrderAssignRuleDO::getWorkOrderType, workOrderAssignRule.getWorkOrderType()));
if (bpmOAWorkOrderAssignRuleDO != null) {
throw exception(THE_WORK_ORDER_RULE_TYPE_ALREADY_EXISTS_PLEASE_DO_NOT_ADD_IT_REPEATEDLY);
}
workOrderAssignRuleMapper.insert(workOrderAssignRule);
// 返回
return workOrderAssignRule.getId();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateWorkOrderAssignRule(BpmOAWorkOrderAssignRuleSaveReqVO updateReqVO) {
// 校验存在
validateWorkOrderAssignRuleExists(updateReqVO.getId());
// 更新
BpmOAWorkOrderAssignRuleDO updateObj = BeanUtils.toBean(updateReqVO, BpmOAWorkOrderAssignRuleDO.class);
workOrderAssignRuleMapper.updateById(updateObj);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteWorkOrderAssignRule(Long id) {
// 校验存在
validateWorkOrderAssignRuleExists(id);
// 删除
workOrderAssignRuleMapper.deleteById(id);
}
private BpmOAWorkOrderAssignRuleDO validateWorkOrderAssignRuleExists(Long id) {
BpmOAWorkOrderAssignRuleDO workOrderAssignRule = workOrderAssignRuleMapper.selectById(id);
if (workOrderAssignRule == null) {
throw exception(OA_WORK_TASK_NOT_EXISTS);
}
return workOrderAssignRule;
}
@Override
public BpmOAWorkOrderAssignRuleDO getWorkOrderAssignRule(Long id) {
return workOrderAssignRuleMapper.selectById(id);
}
@Override
public PageResult<BpmOAWorkOrderAssignRuleRespVO> getWorkOrderAssignRulePage(BpmOAWorkOrderAssignRulePageReqVO pageReqVO) {
PageResult<BpmOAWorkOrderAssignRuleDO> pageResult = workOrderAssignRuleMapper.selectPage(pageReqVO);
return new PageResult<>(convertList(pageResult.getList()), pageResult.getTotal());
}
@Override
public List<BpmOAWorkOrderAssignRuleRespVO> getWorkOrderAssignRuleList(BpmOAWorkOrderAssignRulePageReqVO exportReqVO) {
List<BpmOAWorkOrderAssignRuleDO> list = workOrderAssignRuleMapper.selectList(new LambdaQueryWrapperX<BpmOAWorkOrderAssignRuleDO>()
.likeIfPresent(BpmOAWorkOrderAssignRuleDO::getWorkOrderType, exportReqVO.getWorkOrderType())
.eqIfPresent(BpmOAWorkOrderAssignRuleDO::getDeptId, exportReqVO.getDeptId())
.eqIfPresent(BpmOAWorkOrderAssignRuleDO::getAssigneeUserId, exportReqVO.getAssigneeUserId())
.betweenIfPresent(BpmOAWorkOrderAssignRuleDO::getCreateTime, exportReqVO.getCreateTime()));
return convertList(list);
}
@Override
public List<BpmOAWorkOrderAssignRuleDO> getEnabledRulesByType(String workOrderType) {
return workOrderAssignRuleMapper.selectRulesByType(workOrderType);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateRuleStatus(Collection<Long> ids, Integer status) {
// 批量更新状态
ids.forEach(id -> {
BpmOAWorkOrderAssignRuleDO updateObj = new BpmOAWorkOrderAssignRuleDO();
updateObj.setId(id);
workOrderAssignRuleMapper.updateById(updateObj);
});
}
private List<BpmOAWorkOrderAssignRuleRespVO> convertList(List<BpmOAWorkOrderAssignRuleDO> list) {
if (list.isEmpty()) {
return java.util.Collections.emptyList();
}
// 获取用户信息
Map<Long, AdminUserRespDTO> userMap = convertMap(
userApi.getUserList(list.stream().map(BpmOAWorkOrderAssignRuleDO::getAssigneeUserId).collect(Collectors.toSet())).getCheckedData(),
AdminUserRespDTO::getId);
// 获取部门信息
Map<Long, DeptRespDTO> deptMap = convertMap(
deptApi.getDeptList(list.stream().map(BpmOAWorkOrderAssignRuleDO::getDeptId).collect(Collectors.toSet())).getCheckedData(),
DeptRespDTO::getId);
// 获取字典信息
Map<String, DictDataRespDTO> workOrderTypeMap = convertMap(
dictDataApi.getDictDataList(WORK_ORDER_TYPE).getCheckedData(),
DictDataRespDTO::getValue);
Map<String, DictDataRespDTO> statusMap = convertMap(
dictDataApi.getDictDataList(WORK_ORDER_ASSIGN_RULE_STATUS).getCheckedData(),
DictDataRespDTO::getValue);
return list.stream().map(rule -> {
BpmOAWorkOrderAssignRuleRespVO respVO = BeanUtils.toBean(rule, BpmOAWorkOrderAssignRuleRespVO.class);
// 填充用户名称
if (userMap.containsKey(rule.getAssigneeUserId())) {
respVO.setAssigneeUserName(userMap.get(rule.getAssigneeUserId()).getNickname());
}
// 填充部门名称
if (deptMap.containsKey(rule.getDeptId())) {
respVO.setDeptName(deptMap.get(rule.getDeptId()).getName());
}
// 填充工单类型名称
if (workOrderTypeMap.containsKey(rule.getWorkOrderType())) {
respVO.setWorkOrderTypeName(workOrderTypeMap.get(rule.getWorkOrderType()).getLabel());
}
return respVO;
}).collect(Collectors.toList());
}
}

View File

@ -0,0 +1,123 @@
package cn.iocoder.yudao.module.bpm.service.oa;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder.*;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderDO;
import javax.validation.Valid;
/**
* BPM OA 工单 Service 接口
*
* @author 系统
*/
public interface BpmOAWorkOrderService {
/**
* 创建工单
*
* @param userId 发起人ID
* @param createReqVO 创建请求
* @return 工单ID
*/
Long createWorkOrder(Long userId, @Valid BpmOAWorkOrderCreateReqVO createReqVO);
/**
* 更新工单状态结果
*
* @param id 工单ID
* @param result BPM流程结果
*/
void updateWorkOrderResult(Long id, Integer result);
/**
* 分配工单
*
* @param id 工单ID
* @param assigneeUserId 责任人ID
* @param assigneeDeptId 责任部门ID
*/
void assignWorkOrder(Long id, Long assigneeUserId, Long assigneeDeptId);
/**
* 添加工单跟踪信息
*
* @param userId 操作人ID
* @param trackReqVO 跟踪信息
*/
void addTrackInfo(Long userId, @Valid BpmOAWorkOrderTrackReqVO trackReqVO);
/**
* 更新工单
*
* @param updateReqVO 更新请求
*/
void updateWorkOrder(@Valid BpmOAWorkOrderUpdateReqVO updateReqVO);
/**
* 获取工单详情
*
* @param id 工单ID
* @return 工单DO
*/
BpmOAWorkOrderDO getWorkOrder(Long id);
/**
* 获取工单详情带扩展信息
*
* @param id 工单ID
* @return 工单详情VO
*/
BpmOAWorkOrderRespVO getWorkOrderDetail(Long id);
/**
* 根据流程实例ID获取工单
*
* @param processInstanceId 流程实例ID
* @return 工单DO
*/
BpmOAWorkOrderDO getWorkOrderByProcessInstanceId(String processInstanceId);
/**
* 获取工单分页
*
* @param loginUserId 登录用户ID
* @param pageVO 分页请求
* @return 分页结果
*/
PageResult<BpmOAWorkOrderRespVO> getWorkOrderPage(Long loginUserId, BpmOAWorkOrderPageReqVO pageVO);
/**
* 获取我发起的工单分页
*
* @param loginUserId 登录用户ID
* @param pageVO 分页请求
* @return 分页结果
*/
PageResult<BpmOAWorkOrderRespVO> getMyWorkOrderPage(Long loginUserId, BpmOAWorkOrderPageReqVO pageVO);
/**
* 获取分配给我的工单分页
*
* @param loginUserId 登录用户ID
* @param pageVO 分页请求
* @return 分页结果
*/
PageResult<BpmOAWorkOrderRespVO> getAssignedWorkOrderPage(Long loginUserId, BpmOAWorkOrderPageReqVO pageVO);
/**
* 获取我负责工单的数量
*
* @param userId 用户ID
* @return 工单数量
*/
Long getMyAssignedWorkOrderCount(Long userId);
/**
* 工单操作记录跟踪分页列表
*
* @param dto
* @return
*/
PageResult<BpmOAWorkOrderTrackInfo> getTrackPage(@Valid BpmOAWorkOrderTrackReqDTO dto);
}

View File

@ -0,0 +1,451 @@
package cn.iocoder.yudao.module.bpm.service.oa;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.pojo.UploadUserFile;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder.*;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderAssignRuleDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderTrackDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAWorkOrderAssignRuleMapper;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAWorkOrderMapper;
import cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAWorkOrderTrackMapper;
import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
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.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.OA_WORK_TASK_NOT_EXISTS;
/**
* BPM OA 工单 Service 实现类
*
* @author 系统
*/
@Service
@Validated
public class BpmOAWorkOrderServiceImpl extends BpmOABaseService implements BpmOAWorkOrderService {
/**
* 工单对应的流程定义 KEY
*/
public static final String PROCESS_KEY = "work_order";
@Resource
private BpmOAWorkOrderMapper workOrderMapper;
@Resource
private BpmOAWorkOrderAssignRuleMapper assignRuleMapper;
@Resource
private BpmOAWorkOrderTrackMapper trackMapper;
@Resource
private BpmProcessInstanceApi processInstanceApi;
@Resource
private AdminUserApi userApi;
@Resource
private DeptApi deptApi;
@Resource
private DictDataApi dictDataApi;
private static final String WORK_ORDER_TYPE = "work_order_type";
private static final String WORK_ORDER_LEVEL = "work_order_level";
private static final String WORK_ORDER_STATUS = "work_order_status";
@Override
@Transactional(rollbackFor = Exception.class)
public Long createWorkOrder(Long userId, BpmOAWorkOrderCreateReqVO createReqVO) {
// 获取当前登录用户信息
AdminUserRespDTO userRespDTO = userApi.getUser(userId).getCheckedData();
List<UploadUserFile> list = createReqVO.getFileItems();
// 创建工单DO
BpmOAWorkOrderDO workOrder = BpmOAWorkOrderDO.builder()
.title(createReqVO.getTitle())
.type(createReqVO.getType())
.level(createReqVO.getLevel())
.content(createReqVO.getContent())
.expectedTime(createReqVO.getExpectedTime())
.fileItems(new ArrayList<>(list))
.fromUserId(userId)
.fromDeptId(userRespDTO.getDeptId())
.status(2) // 默认状态处理中
.result(BpmProcessInstanceResultEnum.PROCESS.getResult())
.build();
// 自动分配责任人根据规则
autoAssignWorkOrder(workOrder);
workOrderMapper.insert(workOrder);
// 发起 BPM 流程
Map<String, Object> processInstanceVariables = new HashMap<>();
String processInstanceId = processInstanceApi.createProcessInstance(userId,
new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(PROCESS_KEY)
.setVariables(processInstanceVariables).setBusinessKey(String.valueOf(workOrder.getId()))).getCheckedData();
// 更新工单的流程实例ID
workOrderMapper.updateById(new BpmOAWorkOrderDO().setId(workOrder.getId()).setProcessInstanceId(processInstanceId));
// 创建初始跟踪记录
createTrackRecord(workOrder.getId(), userId, userRespDTO.getNickname(), "创建工单", null);
List<UploadUserFile> fileItems = createReqVO.getFileItems();
//这里的逻辑如果fileItems不为空且有数据那么说明是上传了附件的则需要更工作流文件表对应的实例Id
if (fileItems != null && !fileItems.isEmpty()) {
uploadBpmFileProcessInstanceId(processInstanceId, fileItems);
}
return workOrder.getId();
}
@Override
public void updateWorkOrderResult(Long id, Integer result) {
validateWorkOrderExists(id);
// BPM流程完成时将工单状态设置为已完成
int status = result.equals(BpmProcessInstanceResultEnum.APPROVE.getResult()) ? 3 : 4; // 3-已完成4-已取消
LocalDateTime completedTime = result.equals(BpmProcessInstanceResultEnum.APPROVE.getResult()) ? LocalDateTime.now() : null;
workOrderMapper.updateById(new BpmOAWorkOrderDO()
.setId(id)
.setResult(result)
.setStatus(status)
.setCompletedTime(completedTime));
}
@Override
public void assignWorkOrder(Long id, Long assigneeUserId, Long assigneeDeptId) {
validateWorkOrderExists(id);
workOrderMapper.updateById(new BpmOAWorkOrderDO()
.setId(id)
.setAssigneeUserId(assigneeUserId)
.setAssigneeDeptId(assigneeDeptId)
.setStatus(2)); // 2-处理中
// 创建分配跟踪记录
AdminUserRespDTO assigneeUser = userApi.getUser(assigneeUserId).getCheckedData();
createTrackRecord(id, assigneeUserId, assigneeUser.getNickname(), "工单已分配给:" + assigneeUser.getNickname(), null);
}
@Override
public void addTrackInfo(Long userId, BpmOAWorkOrderTrackReqVO trackReqVO) {
AdminUserRespDTO userRespDTO = userApi.getUser(userId).getCheckedData();
createTrackRecord(trackReqVO.getWorkOrderId(), userId, userRespDTO.getNickname()
, trackReqVO.getContent(), null);
}
@Override
public void updateWorkOrder(BpmOAWorkOrderUpdateReqVO updateReqVO) {
validateWorkOrderExists(updateReqVO.getId());
BpmOAWorkOrderDO updateData = BpmOAWorkOrderDO.builder()
.id(updateReqVO.getId())
.status(updateReqVO.getStatus())
.result(updateReqVO.getResult())
.expectedTime(updateReqVO.getExpectedTime())
.completedTime(updateReqVO.getCompletedTime())
.resultDescription(updateReqVO.getResultDescription())
.build();
workOrderMapper.updateById(updateData);
}
@Override
public BpmOAWorkOrderDO getWorkOrder(Long id) {
return workOrderMapper.selectById(id);
}
@Override
public BpmOAWorkOrderRespVO getWorkOrderDetail(Long id) {
BpmOAWorkOrderDO workOrder = validateWorkOrderExists(id);
// 转换为VO
BpmOAWorkOrderRespVO respVO = convertToRespVO(workOrder);
// 获取跟踪记录
List<BpmOAWorkOrderTrackDO> trackList = trackMapper.selectByWorkOrderId(id);
List<BpmOAWorkOrderTrackInfo> trackInfoList = trackList.stream()
.map(this::convertToTrackInfo)
.collect(Collectors.toList());
respVO.setTrackInfo(trackInfoList);
return respVO;
}
@Override
public BpmOAWorkOrderDO getWorkOrderByProcessInstanceId(String processInstanceId) {
return workOrderMapper.selectOne(BpmOAWorkOrderDO::getProcessInstanceId, processInstanceId);
}
@Override
public PageResult<BpmOAWorkOrderRespVO> getWorkOrderPage(Long loginUserId, BpmOAWorkOrderPageReqVO pageVO) {
IPage<BpmOAWorkOrderRespVO> pageResult = workOrderMapper.selectWorkOrderPage(MyBatisUtils.buildPage(pageVO), pageVO.setLoginUserId(loginUserId));
// 使用XML查询直接返回带关联信息的VO数据
return new PageResult<>(pageResult.getRecords(), pageResult.getTotal());
}
@Override
public PageResult<BpmOAWorkOrderRespVO> getMyWorkOrderPage(Long loginUserId, BpmOAWorkOrderPageReqVO pageVO) {
List<DictDataRespDTO> workOrderTypeDictList = dictDataApi.getDictDataList(WORK_ORDER_TYPE).getCheckedData();
Map<String, String> workOrderTypeDictMap = workOrderTypeDictList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel));
List<DictDataRespDTO> workOrderLevelDictList = dictDataApi.getDictDataList(WORK_ORDER_LEVEL).getCheckedData();
Map<String, String> workOrderLevelDictMap = workOrderLevelDictList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel));
List<DictDataRespDTO> workOrderStatusDictList = dictDataApi.getDictDataList(WORK_ORDER_STATUS).getCheckedData();
Map<String, String> workOrderStatusDictMap = workOrderStatusDictList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel));
PageResult<BpmOAWorkOrderRespVO> bpmOAWorkOrderRespVOPageResult = workOrderMapper.selectMyWorkOrderPage(MyBatisUtils.buildPage(pageVO), pageVO.setLoginUserId(loginUserId));
for (BpmOAWorkOrderRespVO bpmOAWorkOrderRespVO : bpmOAWorkOrderRespVOPageResult.getList()) {
bpmOAWorkOrderRespVO.setTypeName(workOrderTypeDictMap.get(bpmOAWorkOrderRespVO.getType()));
bpmOAWorkOrderRespVO.setLevelName(workOrderLevelDictMap.get(String.valueOf(bpmOAWorkOrderRespVO.getLevel())));
bpmOAWorkOrderRespVO.setStatusName(workOrderStatusDictMap.get(String.valueOf(bpmOAWorkOrderRespVO.getStatus())));
}
// 使用XML查询直接返回带关联信息的VO数据
return bpmOAWorkOrderRespVOPageResult;
}
@Override
public PageResult<BpmOAWorkOrderRespVO> getAssignedWorkOrderPage(Long loginUserId, BpmOAWorkOrderPageReqVO pageVO) {
List<DictDataRespDTO> workOrderTypeDictList = dictDataApi.getDictDataList(WORK_ORDER_TYPE).getCheckedData();
Map<String, String> workOrderTypeDictMap = workOrderTypeDictList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel));
List<DictDataRespDTO> workOrderLevelDictList = dictDataApi.getDictDataList(WORK_ORDER_LEVEL).getCheckedData();
Map<String, String> workOrderLevelDictMap = workOrderLevelDictList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel));
List<DictDataRespDTO> workOrderStatusDictList = dictDataApi.getDictDataList(WORK_ORDER_STATUS).getCheckedData();
Map<String, String> workOrderStatusDictMap = workOrderStatusDictList.stream().collect(Collectors.toMap(DictDataRespDTO::getValue, DictDataRespDTO::getLabel));
IPage<BpmOAWorkOrderRespVO> pageResult = workOrderMapper.selectAssignedWorkOrderPage(MyBatisUtils.buildPage(pageVO), pageVO.setLoginUserId(loginUserId));
PageResult<BpmOAWorkOrderRespVO> bpmOAWorkOrderRespVOPageResult =
pageResult != null ? new PageResult<>(pageResult.getRecords(), pageResult.getTotal()) : new PageResult<>();
for (BpmOAWorkOrderRespVO bpmOAWorkOrderRespVO : bpmOAWorkOrderRespVOPageResult.getList()) {
bpmOAWorkOrderRespVO.setTypeName(workOrderTypeDictMap.get(bpmOAWorkOrderRespVO.getType()));
bpmOAWorkOrderRespVO.setLevelName(workOrderLevelDictMap.get(String.valueOf(bpmOAWorkOrderRespVO.getLevel())));
bpmOAWorkOrderRespVO.setStatusName(workOrderStatusDictMap.get(String.valueOf(bpmOAWorkOrderRespVO.getStatus())));
}
// 使用XML查询直接返回带关联信息的VO数据
return bpmOAWorkOrderRespVOPageResult;
}
@Override
public Long getMyAssignedWorkOrderCount(Long userId) {
return workOrderMapper.selectMyAssignedCount(userId);
}
@Override
public PageResult<BpmOAWorkOrderTrackInfo> getTrackPage(BpmOAWorkOrderTrackReqDTO dto) {
PageResult<BpmOAWorkOrderTrackDO> pageResult = trackMapper.selectPage(new PageParam().setPageSize(dto.getPageSize()).setPageNo(dto.getPageNo()),
new LambdaQueryWrapper<BpmOAWorkOrderTrackDO>()
.eq(dto.getWorkOrderId() != null, BpmOAWorkOrderTrackDO::getWorkOrderId, dto.getWorkOrderId()));
return new PageResult<>(pageResult.getList().stream().map(this::convertToTrackInfo).collect(Collectors.toList()), pageResult.getTotal());
}
/**
* 创建跟踪记录
*/
private void createTrackRecord(Long workOrderId, Long operatorId, String operatorName, String content, String attachments) {
BpmOAWorkOrderTrackDO track = BpmOAWorkOrderTrackDO.builder()
.workOrderId(workOrderId)
.operatorId(operatorId)
.operatorName(operatorName)
.content(content)
.attachments(attachments)
.build();
trackMapper.insert(track);
}
/**
* 自动分配工单责任人
*/
private void autoAssignWorkOrder(BpmOAWorkOrderDO workOrder) {
// 根据工单类型查询分配规则
List<BpmOAWorkOrderAssignRuleDO> rules = assignRuleMapper.selectRulesByType(workOrder.getType());
if (!rules.isEmpty()) {
// 选择第一个匹配的规则按优先级排序
BpmOAWorkOrderAssignRuleDO rule = rules.get(0);
workOrder.setAssigneeUserId(rule.getAssigneeUserId());
workOrder.setAssigneeDeptId(rule.getDeptId());
workOrder.setStatus(2); // 2-处理中
}
// 如果没有匹配的规则保持待分配状态
}
/**
* 校验工单是否存在
*/
private BpmOAWorkOrderDO validateWorkOrderExists(Long id) {
BpmOAWorkOrderDO workOrder = workOrderMapper.selectById(id);
if (workOrder == null) {
throw exception(OA_WORK_TASK_NOT_EXISTS); // 暂时复用现有错误码
}
return workOrder;
}
/**
* 上传BPM文件流程实例ID
* 重写父类方法使用父类的实现
*/
@Override
public void uploadBpmFileProcessInstanceId(String processInstanceId, List<UploadUserFile> fileItems) {
super.uploadBpmFileProcessInstanceId(processInstanceId, fileItems);
}
/**
* 转换DO为RespVO
*/
private BpmOAWorkOrderRespVO convertToRespVO(BpmOAWorkOrderDO workOrder) {
if (workOrder == null) {
return null;
}
BpmOAWorkOrderRespVO respVO = new BpmOAWorkOrderRespVO();
respVO.setId(workOrder.getId());
respVO.setTitle(workOrder.getTitle());
respVO.setType(workOrder.getType());
respVO.setLevel(workOrder.getLevel());
respVO.setContent(workOrder.getContent());
respVO.setFromUserId(workOrder.getFromUserId());
respVO.setFromDeptId(workOrder.getFromDeptId());
respVO.setAssigneeUserId(workOrder.getAssigneeUserId());
respVO.setAssigneeDeptId(workOrder.getAssigneeDeptId());
respVO.setStatus(workOrder.getStatus());
respVO.setExpectedTime(workOrder.getExpectedTime());
respVO.setCompletedTime(workOrder.getCompletedTime());
respVO.setResultDescription(workOrder.getResultDescription());
respVO.setResult(workOrder.getResult());
respVO.setProcessInstanceId(workOrder.getProcessInstanceId());
respVO.setCreateTime(workOrder.getCreateTime());
respVO.setUpdateTime(workOrder.getUpdateTime());
respVO.setFileItems(workOrder.getFileItems());
// 设置类型名称
respVO.setTypeName(getWorkOrderTypeName(workOrder.getType()));
// 设置优先级名称
respVO.setLevelName(getWorkOrderLevelName(workOrder.getLevel()));
// 设置状态名称
respVO.setStatusName(getWorkOrderStatusName(workOrder.getStatus()));
// 获取用户和部门名称
if (workOrder.getFromUserId() != null) {
AdminUserRespDTO fromUser = userApi.getUser(workOrder.getFromUserId()).getCheckedData();
respVO.setFromUserName(fromUser.getNickname());
}
if (workOrder.getAssigneeUserId() != null) {
AdminUserRespDTO assigneeUser = userApi.getUser(workOrder.getAssigneeUserId()).getCheckedData();
respVO.setAssigneeUserName(assigneeUser.getNickname());
}
if (workOrder.getFromDeptId() != null) {
DeptRespDTO fromDept = deptApi.getDept(workOrder.getFromDeptId()).getCheckedData();
respVO.setFromDeptName(fromDept.getName());
}
if (workOrder.getAssigneeDeptId() != null) {
DeptRespDTO assigneeDept = deptApi.getDept(workOrder.getAssigneeDeptId()).getCheckedData();
respVO.setAssigneeDeptName(assigneeDept.getName());
}
return respVO;
}
/**
* 转换跟踪记录DO为TrackInfo
*/
private BpmOAWorkOrderTrackInfo convertToTrackInfo(BpmOAWorkOrderTrackDO trackDO) {
if (trackDO == null) {
return null;
}
BpmOAWorkOrderTrackInfo trackInfo = new BpmOAWorkOrderTrackInfo();
trackInfo.setTrackId(String.valueOf(trackDO.getId()));
trackInfo.setOperatorId(trackDO.getOperatorId());
trackInfo.setOperatorName(trackDO.getOperatorName());
trackInfo.setContent(trackDO.getContent());
trackInfo.setTrackTime(trackDO.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
return trackInfo;
}
/**
* 转换分页结果
*/
private PageResult<BpmOAWorkOrderRespVO> convertToPageResult(PageResult<BpmOAWorkOrderDO> pageResult) {
if (pageResult == null || pageResult.getList() == null) {
return new PageResult<>();
}
List<BpmOAWorkOrderRespVO> respList = pageResult.getList().stream()
.map(this::convertToRespVO)
.collect(Collectors.toList());
return new PageResult<>(respList, pageResult.getTotal());
}
/**
* 获取工单类型名称
*/
private String getWorkOrderTypeName(String type) {
if (type == null) {
return "";
}
try {
DictDataRespDTO dictData = dictDataApi.getDictData("work_order_type", type).getCheckedData();
return dictData != null ? dictData.getLabel() : type;
} catch (Exception e) {
// 如果字典查询失败返回原值
return type;
}
}
/**
* 获取工单优先级名称
*/
private String getWorkOrderLevelName(Integer level) {
if (level == null) {
return "";
}
try {
DictDataRespDTO dictData = dictDataApi.getDictData("work_order_level", String.valueOf(level)).getCheckedData();
return dictData != null ? dictData.getLabel() : String.valueOf(level);
} catch (Exception e) {
// 如果字典查询失败返回原值
return String.valueOf(level);
}
}
/**
* 获取工单状态名称
*/
private String getWorkOrderStatusName(Integer status) {
if (status == null) {
return "";
}
try {
DictDataRespDTO dictData = dictDataApi.getDictData("work_order_status", String.valueOf(status)).getCheckedData();
return dictData != null ? dictData.getLabel() : String.valueOf(status);
} catch (Exception e) {
// 如果字典查询失败返回原值
return String.valueOf(status);
}
}
}

View File

@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.bpm.service.oa.listener;
import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent;
import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventListener;
import cn.iocoder.yudao.module.bpm.service.oa.BpmOAWorkOrderService;
import cn.iocoder.yudao.module.bpm.service.oa.BpmOAWorkOrderServiceImpl;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* OA 工单的结果的监听器实现类
*
* @author 系统
*/
@Component
public class BpmOAWorkOrderResultListener extends BpmProcessInstanceResultEventListener {
@Resource
private BpmOAWorkOrderService workOrderService;
@Override
protected String getProcessDefinitionKey() {
return BpmOAWorkOrderServiceImpl.PROCESS_KEY;
}
@Override
protected void onEvent(BpmProcessInstanceResultEvent event) {
workOrderService.updateWorkOrderResult(Long.parseLong(event.getBusinessKey()), event.getResult());
}
}

View File

@ -0,0 +1,192 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAWorkOrderMapper">
<!-- 工单分页查询结果映射 -->
<resultMap id="WorkOrderPageResultMap" type="cn.iocoder.yudao.module.bpm.controller.admin.oa.vo.workorder.BpmOAWorkOrderRespVO">
<id column="id" property="id"/>
<result column="title" property="title"/>
<result column="type" property="type"/>
<result column="level" property="level"/>
<result column="content" property="content"/>
<result column="from_user_id" property="fromUserId"/>
<result column="from_dept_id" property="fromDeptId"/>
<result column="assignee_user_id" property="assigneeUserId"/>
<result column="assignee_dept_id" property="assigneeDeptId"/>
<result column="status" property="status"/>
<result column="expected_time" property="expectedTime"/>
<result column="completed_time" property="completedTime"/>
<result column="result_description" property="resultDescription"/>
<result column="result" property="result"/>
<result column="process_instance_id" property="processInstanceId"/>
<result column="create_time" property="createTime"/>
<result column="update_time" property="updateTime"/>
<!-- 关联用户和部门名称 -->
<result column="from_user_name" property="fromUserName"/>
<result column="from_dept_name" property="fromDeptName"/>
<result column="assignee_user_name" property="assigneeUserName"/>
<result column="assignee_dept_name" property="assigneeDeptName"/>
<!-- 字典名称 -->
<result column="type_name" property="typeName"/>
<result column="level_name" property="levelName"/>
<result column="status_name" property="statusName"/>
</resultMap>
<!-- 工单分页查询SQL所有工单 -->
<select id="selectWorkOrderPage" resultMap="WorkOrderPageResultMap">
SELECT
w.id,
w.title,
w.type,
w.level,
w.content,
w.from_user_id,
w.from_dept_id,
w.assignee_user_id,
w.assignee_dept_id,
w.status,
w.expected_time,
w.completed_time,
w.result_description,
w.result,
w.process_instance_id,
w.create_time,
w.update_time,
fu.nickname AS from_user_name,
fd.name AS from_dept_name,
au.nickname AS assignee_user_name,
ad.name AS assignee_dept_name
FROM bpm_oa_work_order w
LEFT JOIN system_users fu ON w.from_user_id = fu.id
LEFT JOIN system_dept fd ON w.from_dept_id = fd.id
LEFT JOIN system_users au ON w.assignee_user_id = au.id
LEFT JOIN system_dept ad ON w.assignee_dept_id = ad.id
<where>
w.deleted = 0
<if test="req.title != null and req.title != ''">
AND w.title LIKE CONCAT('%', #{req.title}, '%')
</if>
<if test="req.type != null and req.type != ''">
AND w.type = #{req.type}
</if>
<if test="req.status != null">
AND w.status = #{req.status}
</if>
<if test="req.assigneeUserId != null">
AND w.assignee_user_id = #{req.assigneeUserId}
</if>
<if test="req.fromUserId != null">
AND w.from_user_id = #{req.fromUserId}
</if>
<if test="req.level != null">
AND w.level = #{req.level}
</if>
<if test="req.createTime != null and req.createTime.length() > 0">
AND w.create_time BETWEEN #{req.createTime[0]} AND #{req.createTime[1]}
</if>
</where>
ORDER BY w.id DESC
</select>
<!-- 我发起的工单分页查询SQL -->
<select id="selectMyWorkOrderPage" resultMap="WorkOrderPageResultMap">
SELECT
w.id,
w.title,
w.type,
w.level,
w.content,
w.from_user_id,
w.from_dept_id,
w.assignee_user_id,
w.assignee_dept_id,
w.status,
w.expected_time,
w.completed_time,
w.result_description,
w.result,
w.process_instance_id,
w.create_time,
w.update_time,
fu.nickname AS from_user_name,
fd.name AS from_dept_name,
au.nickname AS assignee_user_name,
ad.name AS assignee_dept_name
FROM bpm_oa_work_order w
LEFT JOIN system_users fu ON w.from_user_id = fu.id
LEFT JOIN system_dept fd ON w.from_dept_id = fd.id
LEFT JOIN system_users au ON w.assignee_user_id = au.id
LEFT JOIN system_dept ad ON w.assignee_dept_id = ad.id
<where>
w.deleted = 0 AND w.from_user_id = #{req.loginUserId}
<if test="req.title != null and req.title != ''">
AND w.title LIKE CONCAT('%', #{req.title}, '%')
</if>
<if test="req.type != null and req.type != ''">
AND w.type = #{req.type}
</if>
<if test="req.status != null">
AND w.status = #{req.status}
</if>
<if test="req.createTime != null and req.createTime.size() == 2">
AND w.create_time BETWEEN #{req.createTime[0]} AND #{req.createTime[1]}
</if>
</where>
ORDER BY w.id DESC
</select>
<!-- 分配给我的工单分页查询SQL -->
<select id="selectAssignedWorkOrderPage" resultMap="WorkOrderPageResultMap">
SELECT
w.id,
w.title,
w.type,
w.level,
w.content,
w.from_user_id,
w.from_dept_id,
w.assignee_user_id,
w.assignee_dept_id,
w.status,
w.expected_time,
w.completed_time,
w.result_description,
w.result,
w.process_instance_id,
w.create_time,
w.update_time,
fu.nickname AS from_user_name,
fd.name AS from_dept_name,
au.nickname AS assignee_user_name,
ad.name AS assignee_dept_name
FROM bpm_oa_work_order w
LEFT JOIN system_users fu ON w.from_user_id = fu.id
LEFT JOIN system_dept fd ON w.from_dept_id = fd.id
LEFT JOIN system_users au ON w.assignee_user_id = au.id
LEFT JOIN system_dept ad ON w.assignee_dept_id = ad.id
<where>
w.deleted = 0 AND w.assignee_user_id = #{req.loginUserId}
<if test="req.title != null and req.title != ''">
AND w.title LIKE CONCAT('%', #{req.title}, '%')
</if>
<if test="req.type != null and req.type != ''">
AND w.type = #{req.type}
</if>
<if test="req.status != null">
AND w.status = #{req.status}
</if>
<if test="req.createTime != null and req.createTime.size() == 2">
AND w.create_time BETWEEN #{req.createTime[0]} AND #{req.createTime[1]}
</if>
</where>
ORDER BY w.id DESC
</select>
<select id="selectByProcessInstanceId"
resultType="cn.iocoder.yudao.module.bpm.dal.dataobject.oa.BpmOAWorkOrderDO">
select a.*
from bpm_oa_work_order a
where a.deleted = 0
and a.process_instance_id = #{processInstanceId}
</select>
</mapper>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.bpm.dal.mysql.oa.BpmOAWorkOrderTrackMapper">
<!-- 工单分页查询结果映射 -->
</mapper>

Binary file not shown.

View File

@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.infra.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* MinIO 配置属性类
*
* @author AI Assistant
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioConfigProperties {
/**
* MinIO 服务地址
*/
private String endpoint;
/**
* 访问密钥
*/
private String accessKey;
/**
* 密钥
*/
private String secretKey;
/**
* 默认存储桶名称
*/
private String bucketName;
}

View File

@ -13,6 +13,7 @@ import cn.iocoder.yudao.framework.security.config.SecurityProperties;
import cn.iocoder.yudao.module.infra.controller.admin.file.vo.file.*;
import cn.iocoder.yudao.module.infra.dal.dataobject.file.*;
import cn.iocoder.yudao.module.infra.service.file.FileService;
import cn.iocoder.yudao.module.infra.service.minio.MinioService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.PostApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
@ -39,6 +40,7 @@ import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.time.LocalDate;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.error;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -66,12 +68,15 @@ public class FileController {
@Resource
private SecurityProperties securityProperties;
@Resource
private MinioService minioService;
@PostMapping("/uploadBpmFileProcessInstanceId")
@Operation(summary = "更新文件的流程实例ID")
@OperateLog(logArgs = false) // 上传文件没有记录操作日志的必要
public String uploadBpmFileProcessInstanceId(@Valid @RequestBody BpmFileUploadReqVO reqVO) throws Exception {
fileService.uploadBpmFileProcessInstanceId(reqVO);
return "success" ;
fileService.uploadBpmFileProcessInstanceId(reqVO);
return "success";
}
@PostMapping("/bpmUpload")
@ -102,7 +107,7 @@ public class FileController {
@GetMapping("/{configId}/get/**")
@PermitAll
@Operation(summary = "下载文件")
@Parameter(name = "configId", description = "配置编号", required = true)
@Parameter(name = "configId", description = "配置编号", required = true)
public void getFileContent(HttpServletRequest request,
HttpServletResponse response,
@PathVariable("configId") Long configId) throws Exception {
@ -146,8 +151,8 @@ public class FileController {
@Operation(summary = "上传业务类型附件【如:工作日/周报附件】")
@OperateLog(logArgs = false) // 上传文件没有记录操作日志的必要
@PermitAll
public CommonResult<BusinessFileDO> businessUpload(@RequestParam("uploadFiles") MultipartFile file,@RequestParam("businessType")Long businessType) throws Exception {
return success(fileService.createBusinessReturnFile(file,businessType));
public CommonResult<BusinessFileDO> businessUpload(@RequestParam("uploadFiles") MultipartFile file, @RequestParam("businessType") Long businessType) throws Exception {
return success(fileService.createBusinessReturnFile(file, businessType));
}
@DeleteMapping("/deleteBusinessFile")
@ -164,7 +169,7 @@ public class FileController {
@OperateLog(logArgs = false) // 上传文件没有记录操作日志的必要
public String uploadBusinessFileProcessInstanceId(@Valid @RequestBody BusinessFileUploadReqVO reqVO) throws Exception {
fileService.uploadBusinessFileProcessInstanceId(reqVO);
return "success" ;
return "success";
}
//add by yj 2024 04-11 End
@ -194,7 +199,6 @@ public class FileController {
@RequestParam(value = "postId", required = false) Long postId) {
// 查询当前部门编号下 是否存在小程序码
QRCodeDO qrCodeDO = fileService.getQRCode(deptId, null);
@ -213,7 +217,7 @@ public class FileController {
return error(OA_QRCODE_ERROR);
}
}else { // 存在的时候 判断是否已存在相同参数的小程序码
} else { // 存在的时候 判断是否已存在相同参数的小程序码
// 查询是否存在 参数一致的小程序码
qrCodeDO = fileService.getQRCode(deptId, scene.toString());
@ -226,7 +230,7 @@ public class FileController {
return error(OA_QRCODE_ERROR);
}
}else {
} else {
fileService.updateQRCodeFile(qrCodeDO.getId());
}
}
@ -296,4 +300,49 @@ public class FileController {
// 上传小程序码 获得url
return fileService.updateQRCodeFile(id, deptId, QRCode.getName(), content);
}
@GetMapping("/presigned-url")
@Operation(summary = "生成预签名上传凭证")
public CommonResult<Map<String, Object>> generatePresignedUrl(
@RequestParam String fileName,
@RequestParam String fileType) {
try {
Map<String, Object> credentials = minioService.generatePresignedUploadCredentials(fileName, fileType);
return success(credentials);
} catch (Exception e) {
log.error("生成预签名上传凭证失败", e);
return CommonResult.error(500, "生成预签名上传凭证失败: " + e.getMessage());
}
}
@GetMapping("/presigned-download-url")
@Operation(summary = "获取MinIO预签名下载URL")
@PermitAll
public CommonResult<Map<String, Object>> generatePresignedDownloadUrl(
@RequestParam("objectName") String objectName) {
try {
// 参数验证
if (objectName == null || objectName.trim().isEmpty()) {
return CommonResult.error(400, "参数错误objectName不能为空");
}
// 调用MinioService生成预签名下载URL
Map<String, Object> result = minioService.generatePresignedDownloadUrl(objectName);
return success(result);
} catch (RuntimeException e) {
String errorMessage = e.getMessage();
if (errorMessage != null && errorMessage.contains("文件不存在")) {
log.warn("文件不存在: {}", objectName);
return CommonResult.error(404, "文件不存在");
} else {
log.error("生成预签名下载URL失败", e);
return CommonResult.error(500, "生成预签名下载URL失败: " + errorMessage);
}
} catch (Exception e) {
log.error("生成预签名下载URL失败", e);
return CommonResult.error(500, "生成预签名下载URL失败: " + e.getMessage());
}
}
}

View File

@ -3,17 +3,23 @@ package cn.iocoder.yudao.module.infra.controller.app.file;
import cn.hutool.core.io.IoUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.infra.controller.app.file.vo.AppFileUploadReqVO;
import cn.iocoder.yudao.module.infra.controller.app.file.vo.MinioMultiUploadReqVO;
import cn.iocoder.yudao.module.infra.controller.app.file.vo.MinioUploadReqVO;
import cn.iocoder.yudao.module.infra.controller.app.file.vo.MinioUploadRespVO;
import cn.iocoder.yudao.module.infra.service.file.FileService;
import cn.iocoder.yudao.module.infra.service.minio.MinioService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -27,6 +33,9 @@ public class AppFileController {
@Resource
private FileService fileService;
@Resource
private MinioService minioService;
@PostMapping("/upload")
@Operation(summary = "上传文件")
public CommonResult<String> uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception {
@ -35,4 +44,40 @@ public class AppFileController {
return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));
}
@PostMapping("/minio/upload")
@Operation(summary = "MinIO单文件上传")
public CommonResult<MinioUploadRespVO> minioUpload(MinioUploadReqVO uploadReqVO) {
try {
String fileUrl = minioService.uploadFile(uploadReqVO.getFile(), uploadReqVO.getPath());
return success(MinioUploadRespVO.single(fileUrl));
} catch (Exception e) {
log.error("MinIO文件上传失败", e);
return CommonResult.error(500, "文件上传失败: " + e.getMessage());
}
}
@PostMapping("/minio/upload/multiple")
@Operation(summary = "MinIO多文件上传")
public CommonResult<MinioUploadRespVO> minioUploadMultiple(@RequestParam("files") MultipartFile[] files) {
try {
List<String> fileUrls = minioService.uploadFiles(files);
return success(MinioUploadRespVO.multiple(fileUrls));
} catch (Exception e) {
log.error("MinIO多文件上传失败", e);
return CommonResult.error(500, "文件上传失败: " + e.getMessage());
}
}
@PostMapping("/minio/upload/batch")
@Operation(summary = "MinIO批量文件上传表单方式")
public CommonResult<MinioUploadRespVO> minioUploadBatch(MinioMultiUploadReqVO uploadReqVO) {
try {
List<String> fileUrls = minioService.uploadFiles(uploadReqVO.getFiles());
return success(MinioUploadRespVO.multiple(fileUrls));
} catch (Exception e) {
log.error("MinIO批量文件上传失败", e);
return CommonResult.error(500, "文件上传失败: " + e.getMessage());
}
}
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.infra.controller.app.file.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotEmpty;
/**
* MinIO 多文件上传请求 VO
*
* @author AI Assistant
*/
@Schema(description = "用户 App - MinIO 多文件上传请求")
@Data
public class MinioMultiUploadReqVO {
@Schema(description = "文件数组", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "文件不能为空")
private MultipartFile[] files;
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.infra.controller.app.file.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.NotNull;
/**
* MinIO 文件上传请求 VO
*
* @author AI Assistant
*/
@Schema(description = "用户 App - MinIO 文件上传请求")
@Data
public class MinioUploadReqVO {
@Schema(description = "文件", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "文件不能为空")
private MultipartFile file;
@Schema(description = "文件路径", example = "avatar/user.jpg")
private String path;
}

View File

@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.infra.controller.app.file.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* MinIO 文件上传响应 VO
*
* @author AI Assistant
*/
@Schema(description = "用户 App - MinIO 文件上传响应")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MinioUploadRespVO {
@Schema(description = "文件访问URL", example = "http://113.105.111.100:9000/yudao-bucket/abc123.jpg")
private String url;
@Schema(description = "文件访问URL列表", example = "[\"http://113.105.111.100:9000/yudao-bucket/abc123.jpg\"]")
private List<String> urls;
/**
* 创建单文件上传响应
*/
public static MinioUploadRespVO single(String url) {
return new MinioUploadRespVO(url, null);
}
/**
* 创建多文件上传响应
*/
public static MinioUploadRespVO multiple(List<String> urls) {
return new MinioUploadRespVO(null, urls);
}
}

View File

@ -0,0 +1,286 @@
package cn.iocoder.yudao.module.infra.service.minio;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.infra.config.MinioConfigProperties;
import io.minio.BucketExistsArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MakeBucketArgs;
import io.minio.MinioClient;
import io.minio.PostPolicy;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.http.Method;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.InputStream;
import java.time.Instant;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* MinIO 文件服务类
*
* @author AI Assistant
*/
@Slf4j
@Service
public class MinioService {
@Resource
private MinioConfigProperties minioConfigProperties;
private MinioClient minioClient;
/**
* 初始化 MinIO 客户端
*/
@PostConstruct
public void init() {
try {
this.minioClient = MinioClient.builder()
.endpoint(minioConfigProperties.getEndpoint())
.credentials(minioConfigProperties.getAccessKey(), minioConfigProperties.getSecretKey())
.build();
// 检查桶是否存在不存在则创建
String[] bucketsToCreate = {minioConfigProperties.getBucketName(), "user-uploads"};
for (String bucketName : bucketsToCreate) {
boolean bucketExists = minioClient.bucketExists(
BucketExistsArgs.builder()
.bucket(bucketName)
.build()
);
if (!bucketExists) {
minioClient.makeBucket(
MakeBucketArgs.builder()
.bucket(bucketName)
.build()
);
log.info("创建存储桶: {}", bucketName);
}
}
log.info("MinIO 客户端初始化成功. Endpoint: {}, Bucket: {}",
minioConfigProperties.getEndpoint(), minioConfigProperties.getBucketName());
} catch (Exception e) {
log.error("MinIO 客户端初始化失败", e);
throw new RuntimeException("MinIO 客户端初始化失败", e);
}
}
/**
* 上传单个文件
*
* @param file 上传的文件
* @param path 文件存储路径如果为空则自动生成
* @return 文件访问URL
*/
public String uploadFile(MultipartFile file, String path) throws Exception {
if (file.isEmpty()) {
throw new IllegalArgumentException("上传文件不能为空");
}
// 如果路径为空则生成随机路径
if (path == null || path.trim().isEmpty()) {
String originalFilename = file.getOriginalFilename();
String extension = "";
if (originalFilename != null && originalFilename.contains(".")) {
extension = originalFilename.substring(originalFilename.lastIndexOf("."));
}
path = UUID.randomUUID().toString() + extension;
}
try (InputStream inputStream = file.getInputStream()) {
// 上传文件到 MinIO
minioClient.putObject(
PutObjectArgs.builder()
.bucket(minioConfigProperties.getBucketName())
.object(path)
.stream(inputStream, file.getSize(), -1)
.contentType(file.getContentType())
.build()
);
// 生成文件访问URL
String fileUrl = minioConfigProperties.getEndpoint() + "/" +
minioConfigProperties.getBucketName() + "/" + path;
log.info("文件上传成功: {}", fileUrl);
return fileUrl;
} catch (Exception e) {
log.error("文件上传失败: {}", path, e);
throw new Exception("文件上传失败: " + e.getMessage(), e);
}
}
/**
* 上传多个文件
*
* @param files 上传的文件数组
* @return 文件访问URL列表
*/
public List<String> uploadFiles(MultipartFile[] files) throws Exception {
if (files == null || files.length == 0) {
throw new IllegalArgumentException("上传文件不能为空");
}
List<String> fileUrls = new ArrayList<>();
for (MultipartFile file : files) {
if (!file.isEmpty()) {
String fileUrl = uploadFile(file, null);
fileUrls.add(fileUrl);
}
}
log.info("批量文件上传完成,共上传 {} 个文件", fileUrls.size());
return fileUrls;
}
/**
* 删除文件
*
* @param path 文件路径
*/
public void deleteFile(String path) throws Exception {
try {
minioClient.removeObject(
RemoveObjectArgs.builder()
.bucket(minioConfigProperties.getBucketName())
.object(path)
.build()
);
log.info("文件删除成功: {}", path);
} catch (Exception e) {
log.error("文件删除失败: {}", path, e);
throw new Exception("文件删除失败: " + e.getMessage(), e);
}
}
/**
* 生成预签名上传凭证
*
* @param fileName 文件名
* @param fileType 文件类型
* @return 预签名上传表单数据
*/
public Map<String, Object> generatePresignedUploadCredentials(String fileName, String fileType) {
try {
// 1. 安全验证 - 从SecurityContext获取当前用户
Long userId = SecurityFrameworkUtils.getLoginUserId();
if (userId == null) {
throw new IllegalStateException("用户未登录");
}
// 2. 生成唯一对象名
String sanitizedFileName = sanitizeFilename(fileName);
String objectName = String.format("user_%d/%s_%s", userId, UUID.randomUUID(), sanitizedFileName);
// 3. 创建PostPolicy
// 使用固定的 user-uploads bucket 用于用户上传
String bucketName = "user-uploads";
PostPolicy policy = new PostPolicy(bucketName, ZonedDateTime.now().plusHours(1));
// 设置对象名
policy.addEqualsCondition("key", objectName);
// 添加文件大小限制最小1字节最大10GB
policy.addContentLengthRangeCondition(1, 10L * 1024 * 1024 * 1024);
// 添加Content-Type条件
policy.addEqualsCondition("Content-Type", fileType);
// 4. 调用MinIO客户端获取预签名表单数据
Map<String, String> formData = minioClient.getPresignedPostFormData(policy);
// 5. 构建返回结构
Map<String, Object> result = new java.util.HashMap<>();
result.put("url", minioConfigProperties.getEndpoint() + "/" + bucketName);
result.put("fields", formData);
result.put("objectName", objectName);
log.info("生成预签名上传凭证成功用户ID: {}, 对象名: {}", userId, objectName);
return result;
} catch (Exception e) {
log.error("生成预签名上传凭证失败", e);
throw new RuntimeException("生成预签名上传凭证失败: " + e.getMessage(), e);
}
}
/**
* 文件名安全处理
*
* @param fileName 原文件名
* @return 安全的文件名
*/
private String sanitizeFilename(String fileName) {
if (fileName == null || fileName.trim().isEmpty()) {
return "unknown";
}
return fileName.replaceAll("[^a-zA-Z0-9.-]", "_");
}
/**
* 生成预签名下载URL
*
* @param objectName 对象名称
* @return 包含下载URL等信息的Map
*/
public Map<String, Object> generatePresignedDownloadUrl(String objectName) {
try {
// 1. 参数验证
if (objectName == null || objectName.trim().isEmpty()) {
throw new IllegalArgumentException("对象名称不能为空");
}
// 3. 验证文件是否存在
String bucketName = "user-uploads";
try {
StatObjectResponse stat = minioClient.statObject(
StatObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build()
);
log.debug("文件存在检查通过: {}, 大小: {}", objectName, stat.size());
} catch (Exception e) {
log.warn("文件不存在: {}", objectName);
throw new RuntimeException("文件不存在");
}
// 4. 生成预签名下载URL
String downloadUrl = minioClient.getPresignedObjectUrl(
GetPresignedObjectUrlArgs.builder()
.method(Method.GET)
.bucket(bucketName)
.object(objectName)
.build()
);
// 6. 构建返回结果
Map<String, Object> result = new java.util.HashMap<>();
result.put("objectName", objectName);
result.put("downloadUrl", downloadUrl);
log.info("生成预签名下载URL成功对象名: {}", objectName);
return result;
} catch (Exception e) {
log.error("生成预签名下载URL失败对象名: {}", objectName, e);
throw new RuntimeException("生成预签名下载URL失败: " + e.getMessage(), e);
}
}
}

View File

@ -110,6 +110,15 @@ spring:
# Spring Boot Admin Server 服务端的相关配置
context-path: /admin # 配置 Spring
# MinIO 配置项
minio:
endpoint: http://113.105.111.100:9000 # MinIO 服务地址
access-key: 6kyyo7KvGZ5tFKfuxUl2 # 访问密钥
secret-key: PmAFxtRcBlvg5ZGPlnzydQrnKtj1PQGHnd7x8hx7 # 密钥
bucket-name: dev-bucket # 默认存储桶名称
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置

View File

@ -1,5 +1,11 @@
--- #################### 数据库相关配置 ####################
spring:
# Servlet 配置
servlet:
# 文件上传相关配置项
multipart:
max-file-size: 5GB # 单个文件大小
max-request-size: 10GB # 设置总上传的文件大小
# 数据源配置项
autoconfigure:
@ -122,6 +128,15 @@ logging:
cn.iocoder.yudao.module.infra.dal.mysql: debug
cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper: INFO # 配置 FileConfigMapper 的日志级别为 info
--- #################### MinIO 文件服务配置 ####################
# MinIO 配置项
minio:
endpoint: http://10.10.2.3:9000 # MinIO 服务地址
access-key: fKpfyQYqJn7jSId2WDhn # 访问密钥
secret-key: XkTbVLD1pgvxXphFIjgpIYKks166o9zGrDFqTz3t # 密钥
bucket-name: test-bucket # 默认存储桶名称
--- #################### 芋道相关配置 ####################
# 芋道配置项,设置当前项目所有自定义的配置

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,9 +1,12 @@
package cn.iocoder.yudao.module.system.controller.admin.attendance;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.address.AddressUtil;
import cn.iocoder.yudao.module.system.controller.admin.attendance.dto.*;
import cn.iocoder.yudao.module.system.controller.admin.attendance.vo.*;
import cn.iocoder.yudao.module.system.service.attendance.AttendanceService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
@ -119,4 +122,19 @@ public class AttendanceController {
attendanceService.generateHolidayToRedis(year);
return success("ok");
}
@GetMapping("/addressInverseResolution")
@Operation(summary = "地址逆解析")
@PermitAll
public CommonResult<?> addressInverseResolution(@RequestParam(name = "longitude") double longitude, @RequestParam(name = "latitude") double latitude) {
String result = "";
try {
result = AddressUtil.reverseGeocode(longitude, latitude);
} catch (Exception e) {
System.err.println("逆地理编码失败:");
e.printStackTrace();
}
JSONObject jsonObject = JSON.parseObject(result);
return success(jsonObject);
}
}

View File

@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.system.controller.admin.attendance.vo.*;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.addressgroup.AttendanceAddressGroupItemDO;
import cn.iocoder.yudao.module.system.dal.dataobject.attendance.groupshiftitem.AttendanceGroupShiftItemDO;
import cn.iocoder.yudao.module.system.service.attendance.punch.dto.AttendanceOnTheDayDTO;
import com.google.gson.JsonObject;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;

BIN
yudao-ui/.DS_Store vendored

Binary file not shown.

View File

@ -1,4 +0,0 @@
基于 Vue + uni-app 实现的管理后台。仓库地址:
* Gitee<https://gitee.com/yudaocode/yudao-ui-admin-uniapp>
* GitHub<https://github.com/yudaocode/yudao-ui-admin-uniapp>

View File

@ -1,4 +0,0 @@
基于 Vue3 + vben(ant-design-vue) 实现的管理后台。仓库地址:
* Gitee<https://gitee.com/yudaocode/yudao-ui-admin-vben>
* GitHub<https://github.com/yudaocode/yudao-ui-admin-vben>

View File

@ -1,4 +0,0 @@
基于 Vue2 + element-ui 实现的管理后台。仓库地址:
* Gitee<https://gitee.com/yudaocode/yudao-ui-admin-vue2>
* GitHub<https://github.com/yudaocode/yudao-ui-admin-vue2>

View File

@ -1,4 +0,0 @@
基于 Vue3 + element-plus 实现的管理后台。仓库地址:
* Gitee<https://gitee.com/yudaocode/yudao-ui-admin-vue3>
* GitHub<https://github.com/yudaocode/yudao-ui-admin-vue3>

View File

@ -1,8 +0,0 @@
仓库地址:
* Gitee<https://gitee.com/yudaocode/yudao-mall-uniapp>
* GitHub<https://github.com/yudaocode/yudao-mall-uniapp>
功能列表:
* 基于 uniapp 开发支持微信小程序、微信公众号、H5 移动端,未来会支持支付宝小程序、抖音小程序等
* 支持 SaaS 多租户,可满足商品、订单、支付、会员、优惠券、秒杀、拼团、砍价、分销、积分等多种经营需求