feat(risk): 实现风险管理模块功能
- 新增RiskMapper,定义风险相关SQL映射和查询功能 - 添加CreateRiskRequest、CreateWorkOrderRequest和ProcessWorkOrderRequest请求DTO - 实现RiskController,支持风险的创建、更新、删除、详细查询及列表分页查询 - 提供风险统计接口,统计风险总数、状态分布和等级分布 - 增加风险分配工单及批量更新状态的接口 - 实现RiskService及其实现类,包含风险的增删改查及业务逻辑 - 计算风险得分和风险等级,并支持标签和工单关联管理 - 定义RiskVO和RiskStatisticsVO用于前端数据展示和统计 - 实现风险工单的创建和管理,关联风险状态同步更新
This commit is contained in:
199
src/main/java/cn/yinlihupo/controller/risk/RiskController.java
Normal file
199
src/main/java/cn/yinlihupo/controller/risk/RiskController.java
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
package cn.yinlihupo.controller.risk;
|
||||||
|
|
||||||
|
import cn.yinlihupo.common.core.BaseResponse;
|
||||||
|
import cn.yinlihupo.common.page.TableDataInfo;
|
||||||
|
import cn.yinlihupo.common.util.ResultUtils;
|
||||||
|
import cn.yinlihupo.common.util.SecurityUtils;
|
||||||
|
import cn.yinlihupo.domain.dto.CreateRiskRequest;
|
||||||
|
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
|
||||||
|
import cn.yinlihupo.domain.vo.RiskStatisticsVO;
|
||||||
|
import cn.yinlihupo.domain.vo.RiskVO;
|
||||||
|
import cn.yinlihupo.service.risk.RiskService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险管理控制器
|
||||||
|
* 提供风险新建、查询、统计、分配工单等功能
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/risk")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RiskController {
|
||||||
|
|
||||||
|
private final RiskService riskService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建风险评估
|
||||||
|
*
|
||||||
|
* @param request 创建请求
|
||||||
|
* @return 风险ID
|
||||||
|
*/
|
||||||
|
@PostMapping
|
||||||
|
public BaseResponse<Long> createRisk(@RequestBody CreateRiskRequest request) {
|
||||||
|
log.info("创建风险评估, projectId: {}", request.getProjectId());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Long riskId = riskService.createRisk(request);
|
||||||
|
return ResultUtils.success("创建成功", riskId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建风险评估失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("创建失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新风险
|
||||||
|
*
|
||||||
|
* @param riskId 风险ID
|
||||||
|
* @param request 更新请求
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
@PutMapping("/{riskId}")
|
||||||
|
public BaseResponse<Boolean> updateRisk(@PathVariable Long riskId, @RequestBody CreateRiskRequest request) {
|
||||||
|
log.info("更新风险, riskId: {}", riskId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Boolean result = riskService.updateRisk(riskId, request);
|
||||||
|
return ResultUtils.success("更新成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新风险失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("更新失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除风险
|
||||||
|
*
|
||||||
|
* @param riskId 风险ID
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/{riskId}")
|
||||||
|
public BaseResponse<Boolean> deleteRisk(@PathVariable Long riskId) {
|
||||||
|
log.info("删除风险, riskId: {}", riskId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Boolean result = riskService.deleteRisk(riskId);
|
||||||
|
return ResultUtils.success("删除成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("删除风险失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取风险详情
|
||||||
|
*
|
||||||
|
* @param riskId 风险ID
|
||||||
|
* @return 风险详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/{riskId}")
|
||||||
|
public BaseResponse<RiskVO> getRiskDetail(@PathVariable Long riskId) {
|
||||||
|
log.info("获取风险详情, riskId: {}", riskId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
RiskVO result = riskService.getRiskDetail(riskId);
|
||||||
|
return ResultUtils.success("查询成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取风险详情失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询风险列表
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param pageNum 页码
|
||||||
|
* @param pageSize 每页大小
|
||||||
|
* @param category 分类筛选
|
||||||
|
* @param riskLevel 风险等级筛选
|
||||||
|
* @param status 状态筛选
|
||||||
|
* @param keyword 关键词搜索
|
||||||
|
* @return 分页风险列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public BaseResponse<TableDataInfo<RiskVO>> getRiskList(
|
||||||
|
@RequestParam(required = false) Long projectId,
|
||||||
|
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||||
|
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||||
|
@RequestParam(required = false) String category,
|
||||||
|
@RequestParam(required = false) String riskLevel,
|
||||||
|
@RequestParam(required = false) String status,
|
||||||
|
@RequestParam(required = false) String keyword) {
|
||||||
|
|
||||||
|
log.info("分页查询风险列表, projectId: {}, pageNum: {}, pageSize: {}", projectId, pageNum, pageSize);
|
||||||
|
|
||||||
|
try {
|
||||||
|
TableDataInfo<RiskVO> result = riskService.getRiskList(projectId, pageNum, pageSize,
|
||||||
|
category, riskLevel, status, keyword);
|
||||||
|
return ResultUtils.success("查询成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询风险列表失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取风险统计信息
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @return 统计信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/statistics")
|
||||||
|
public BaseResponse<RiskStatisticsVO> getRiskStatistics(@RequestParam(required = false) Long projectId) {
|
||||||
|
log.info("获取风险统计信息, projectId: {}", projectId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
RiskStatisticsVO result = riskService.getRiskStatistics(projectId);
|
||||||
|
return ResultUtils.success("查询成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取风险统计失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为风险分配工单
|
||||||
|
*
|
||||||
|
* @param riskId 风险ID
|
||||||
|
* @param request 工单创建请求
|
||||||
|
* @return 工单ID
|
||||||
|
*/
|
||||||
|
@PostMapping("/{riskId}/assign-workorder")
|
||||||
|
public BaseResponse<Long> assignWorkOrder(@PathVariable Long riskId, @RequestBody CreateWorkOrderRequest request) {
|
||||||
|
log.info("为风险分配工单, riskId: {}", riskId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Long workOrderId = riskService.assignWorkOrder(riskId, request);
|
||||||
|
return ResultUtils.success("分配成功", workOrderId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("分配工单失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("分配失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新风险状态
|
||||||
|
*
|
||||||
|
* @param riskIds 风险ID列表
|
||||||
|
* @param status 新状态
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
@PutMapping("/batch-status")
|
||||||
|
public BaseResponse<Boolean> batchUpdateStatus(@RequestBody List<Long> riskIds, @RequestParam String status) {
|
||||||
|
log.info("批量更新风险状态, riskIds: {}, status: {}", riskIds, status);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Boolean result = riskService.batchUpdateStatus(riskIds, status);
|
||||||
|
return ResultUtils.success("更新成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("批量更新状态失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("更新失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
package cn.yinlihupo.controller.risk;
|
||||||
|
|
||||||
|
import cn.yinlihupo.common.core.BaseResponse;
|
||||||
|
import cn.yinlihupo.common.page.TableDataInfo;
|
||||||
|
import cn.yinlihupo.common.util.ResultUtils;
|
||||||
|
import cn.yinlihupo.common.util.SecurityUtils;
|
||||||
|
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
|
||||||
|
import cn.yinlihupo.domain.dto.ProcessWorkOrderRequest;
|
||||||
|
import cn.yinlihupo.domain.vo.WorkOrderStatisticsVO;
|
||||||
|
import cn.yinlihupo.domain.vo.WorkOrderVO;
|
||||||
|
import cn.yinlihupo.service.risk.WorkOrderService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单管理控制器
|
||||||
|
* 提供工单创建、查询、处理等功能
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/workorder")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WorkOrderController {
|
||||||
|
|
||||||
|
private final WorkOrderService workOrderService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建工单
|
||||||
|
*
|
||||||
|
* @param request 创建请求
|
||||||
|
* @return 工单ID
|
||||||
|
*/
|
||||||
|
@PostMapping
|
||||||
|
public BaseResponse<Long> createWorkOrder(@RequestBody CreateWorkOrderRequest request) {
|
||||||
|
log.info("创建工单, projectId: {}", request.getProjectId());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Long workOrderId = workOrderService.createWorkOrder(request);
|
||||||
|
return ResultUtils.success("创建成功", workOrderId);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("创建工单失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("创建失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新工单
|
||||||
|
*
|
||||||
|
* @param workOrderId 工单ID
|
||||||
|
* @param request 更新请求
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
@PutMapping("/{workOrderId}")
|
||||||
|
public BaseResponse<Boolean> updateWorkOrder(@PathVariable Long workOrderId, @RequestBody CreateWorkOrderRequest request) {
|
||||||
|
log.info("更新工单, workOrderId: {}", workOrderId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Boolean result = workOrderService.updateWorkOrder(workOrderId, request);
|
||||||
|
return ResultUtils.success("更新成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("更新工单失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("更新失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除工单
|
||||||
|
*
|
||||||
|
* @param workOrderId 工单ID
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/{workOrderId}")
|
||||||
|
public BaseResponse<Boolean> deleteWorkOrder(@PathVariable Long workOrderId) {
|
||||||
|
log.info("删除工单, workOrderId: {}", workOrderId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Boolean result = workOrderService.deleteWorkOrder(workOrderId);
|
||||||
|
return ResultUtils.success("删除成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("删除工单失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("删除失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工单详情
|
||||||
|
*
|
||||||
|
* @param workOrderId 工单ID
|
||||||
|
* @return 工单详情
|
||||||
|
*/
|
||||||
|
@GetMapping("/{workOrderId}")
|
||||||
|
public BaseResponse<WorkOrderVO> getWorkOrderDetail(@PathVariable Long workOrderId) {
|
||||||
|
log.info("获取工单详情, workOrderId: {}", workOrderId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
WorkOrderVO result = workOrderService.getWorkOrderDetail(workOrderId);
|
||||||
|
return ResultUtils.success("查询成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取工单详情失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询工单列表
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param pageNum 页码
|
||||||
|
* @param pageSize 每页大小
|
||||||
|
* @param orderType 类型筛选
|
||||||
|
* @param status 状态筛选
|
||||||
|
* @param priority 优先级筛选
|
||||||
|
* @param keyword 关键词搜索
|
||||||
|
* @return 分页工单列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public BaseResponse<TableDataInfo<WorkOrderVO>> getWorkOrderList(
|
||||||
|
@RequestParam(required = false) Long projectId,
|
||||||
|
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||||
|
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||||
|
@RequestParam(required = false) String orderType,
|
||||||
|
@RequestParam(required = false) String status,
|
||||||
|
@RequestParam(required = false) String priority,
|
||||||
|
@RequestParam(required = false) String keyword) {
|
||||||
|
|
||||||
|
log.info("分页查询工单列表, projectId: {}, pageNum: {}, pageSize: {}", projectId, pageNum, pageSize);
|
||||||
|
|
||||||
|
try {
|
||||||
|
TableDataInfo<WorkOrderVO> result = workOrderService.getWorkOrderList(projectId, pageNum, pageSize,
|
||||||
|
orderType, status, priority, keyword);
|
||||||
|
return ResultUtils.success("查询成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询工单列表失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的工单列表
|
||||||
|
*
|
||||||
|
* @param pageNum 页码
|
||||||
|
* @param pageSize 每页大小
|
||||||
|
* @param status 状态筛选
|
||||||
|
* @param orderType 类型筛选
|
||||||
|
* @return 分页工单列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/my")
|
||||||
|
public BaseResponse<TableDataInfo<WorkOrderVO>> getMyWorkOrders(
|
||||||
|
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||||
|
@RequestParam(defaultValue = "10") Integer pageSize,
|
||||||
|
@RequestParam(required = false) String status,
|
||||||
|
@RequestParam(required = false) String orderType) {
|
||||||
|
|
||||||
|
log.info("获取我的工单列表, pageNum: {}, pageSize: {}", pageNum, pageSize);
|
||||||
|
|
||||||
|
try {
|
||||||
|
TableDataInfo<WorkOrderVO> result = workOrderService.getMyWorkOrders(pageNum, pageSize, status, orderType);
|
||||||
|
return ResultUtils.success("查询成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("查询我的工单失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理工单
|
||||||
|
*
|
||||||
|
* @param request 处理请求
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
@PostMapping("/process")
|
||||||
|
public BaseResponse<Boolean> processWorkOrder(@RequestBody ProcessWorkOrderRequest request) {
|
||||||
|
log.info("处理工单, workOrderId: {}", request.getWorkOrderId());
|
||||||
|
|
||||||
|
try {
|
||||||
|
Boolean result = workOrderService.processWorkOrder(request);
|
||||||
|
return ResultUtils.success("处理成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("处理工单失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("处理失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分配工单给处理人
|
||||||
|
*
|
||||||
|
* @param workOrderId 工单ID
|
||||||
|
* @param handlerId 处理人ID
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
@PutMapping("/{workOrderId}/assign")
|
||||||
|
public BaseResponse<Boolean> assignWorkOrder(@PathVariable Long workOrderId, @RequestParam Long handlerId) {
|
||||||
|
log.info("分配工单, workOrderId: {}, handlerId: {}", workOrderId, handlerId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
Boolean result = workOrderService.assignWorkOrder(workOrderId, handlerId);
|
||||||
|
return ResultUtils.success("分配成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("分配工单失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("分配失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工单统计信息
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID(可选)
|
||||||
|
* @return 统计信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/statistics")
|
||||||
|
public BaseResponse<WorkOrderStatisticsVO> getWorkOrderStatistics(@RequestParam(required = false) Long projectId) {
|
||||||
|
log.info("获取工单统计信息, projectId: {}", projectId);
|
||||||
|
|
||||||
|
try {
|
||||||
|
WorkOrderStatisticsVO result = workOrderService.getWorkOrderStatistics(projectId);
|
||||||
|
return ResultUtils.success("查询成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取工单统计失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的工单统计信息
|
||||||
|
*
|
||||||
|
* @return 统计信息
|
||||||
|
*/
|
||||||
|
@GetMapping("/my/statistics")
|
||||||
|
public BaseResponse<WorkOrderStatisticsVO> getMyWorkOrderStatistics() {
|
||||||
|
log.info("获取我的工单统计信息");
|
||||||
|
|
||||||
|
try {
|
||||||
|
WorkOrderStatisticsVO result = workOrderService.getMyWorkOrderStatistics();
|
||||||
|
return ResultUtils.success("查询成功", result);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取我的工单统计失败: {}", e.getMessage(), e);
|
||||||
|
return ResultUtils.error("查询失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
79
src/main/java/cn/yinlihupo/domain/dto/CreateRiskRequest.java
Normal file
79
src/main/java/cn/yinlihupo/domain/dto/CreateRiskRequest.java
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package cn.yinlihupo.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建风险请求DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CreateRiskRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目ID
|
||||||
|
*/
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险分类: technical-技术风险, schedule-进度风险, cost-成本风险, quality-质量风险, resource-资源风险, external-外部风险, other-其他
|
||||||
|
*/
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险名称
|
||||||
|
*/
|
||||||
|
private String riskName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险来源: internal-内部, external-外部, manual-手动添加
|
||||||
|
*/
|
||||||
|
private String riskSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发生概率(0-100)
|
||||||
|
*/
|
||||||
|
private Integer probability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 影响程度(1-5)
|
||||||
|
*/
|
||||||
|
private Integer impact;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 负责人ID
|
||||||
|
*/
|
||||||
|
private Long ownerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓解措施
|
||||||
|
*/
|
||||||
|
private String mitigationPlan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应急计划
|
||||||
|
*/
|
||||||
|
private String contingencyPlan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发条件
|
||||||
|
*/
|
||||||
|
private String triggerCondition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预期解决日期
|
||||||
|
*/
|
||||||
|
private LocalDate dueDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签
|
||||||
|
*/
|
||||||
|
private List<String> tags;
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package cn.yinlihupo.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建工单请求DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CreateWorkOrderRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目ID
|
||||||
|
*/
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联风险ID
|
||||||
|
*/
|
||||||
|
private Long riskId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单类型: bug-缺陷, feature-需求, task-任务, incident-事件, risk_handle-风险处理, other-其他
|
||||||
|
*/
|
||||||
|
private String orderType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级: critical-紧急, high-高, medium-中, low-低
|
||||||
|
*/
|
||||||
|
private String priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理人ID
|
||||||
|
*/
|
||||||
|
private Long handlerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理组ID
|
||||||
|
*/
|
||||||
|
private Long handlerGroupId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 截止时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime deadline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 来源: web-网页, mobile-移动端, api-接口, system-系统生成, risk-风险分派
|
||||||
|
*/
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签
|
||||||
|
*/
|
||||||
|
private List<String> tags;
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package cn.yinlihupo.domain.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理工单请求DTO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ProcessWorkOrderRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单ID
|
||||||
|
*/
|
||||||
|
private Long workOrderId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理状态: processing-处理中, resolved-已解决, closed-已关闭, reopened-已重开
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 满意度评分(1-5)
|
||||||
|
*/
|
||||||
|
private Integer satisfactionScore;
|
||||||
|
}
|
||||||
146
src/main/java/cn/yinlihupo/domain/entity/WorkOrder.java
Normal file
146
src/main/java/cn/yinlihupo/domain/entity/WorkOrder.java
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package cn.yinlihupo.domain.entity;
|
||||||
|
|
||||||
|
import cn.yinlihupo.common.handler.JsonbTypeHandler;
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单实体类
|
||||||
|
* 对应数据库表: work_order
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("work_order")
|
||||||
|
public class WorkOrder {
|
||||||
|
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单编号
|
||||||
|
*/
|
||||||
|
private String orderCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单类型: bug-缺陷, feature-需求, task-任务, incident-事件, risk_handle-风险处理, other-其他
|
||||||
|
*/
|
||||||
|
private String orderType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目ID
|
||||||
|
*/
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联风险ID
|
||||||
|
*/
|
||||||
|
private Long riskId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人ID
|
||||||
|
*/
|
||||||
|
private Long creatorId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理人ID
|
||||||
|
*/
|
||||||
|
private Long handlerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理组ID
|
||||||
|
*/
|
||||||
|
private Long handlerGroupId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级: critical-紧急, high-高, medium-中, low-低
|
||||||
|
*/
|
||||||
|
private String priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态: pending-待处理, assigned-已分派, processing-处理中, resolved-已解决, closed-已关闭, reopened-已重开
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 来源: web-网页, mobile-移动端, api-接口, system-系统生成, risk-风险分派
|
||||||
|
*/
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 截止时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime deadline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分派时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime assignedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首次响应时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime firstResponseTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解决时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime resolvedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime closedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 满意度评分(1-5)
|
||||||
|
*/
|
||||||
|
private Integer satisfactionScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签
|
||||||
|
*/
|
||||||
|
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||||
|
private List<String> tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 附件列表
|
||||||
|
*/
|
||||||
|
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||||
|
private List<Object> attachments;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扩展数据
|
||||||
|
*/
|
||||||
|
@TableField(typeHandler = JsonbTypeHandler.class)
|
||||||
|
private Object extraData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除标记
|
||||||
|
*/
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
}
|
||||||
87
src/main/java/cn/yinlihupo/domain/vo/RiskStatisticsVO.java
Normal file
87
src/main/java/cn/yinlihupo/domain/vo/RiskStatisticsVO.java
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package cn.yinlihupo.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险统计VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RiskStatisticsVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险总数
|
||||||
|
*/
|
||||||
|
private Integer totalCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已识别数量
|
||||||
|
*/
|
||||||
|
private Integer identifiedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已分派工单数量
|
||||||
|
*/
|
||||||
|
private Integer assignedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓解中数量
|
||||||
|
*/
|
||||||
|
private Integer mitigatingCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已解决数量
|
||||||
|
*/
|
||||||
|
private Integer resolvedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已关闭数量
|
||||||
|
*/
|
||||||
|
private Integer closedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 严重风险数量
|
||||||
|
*/
|
||||||
|
private Integer criticalCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高风险数量
|
||||||
|
*/
|
||||||
|
private Integer highCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 中风险数量
|
||||||
|
*/
|
||||||
|
private Integer mediumCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 低风险数量
|
||||||
|
*/
|
||||||
|
private Integer lowCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按分类统计
|
||||||
|
*/
|
||||||
|
private Object categoryStats;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按等级统计
|
||||||
|
*/
|
||||||
|
private Object levelStats;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理趋势(近6个月)
|
||||||
|
*/
|
||||||
|
private Object trendData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平均风险得分
|
||||||
|
*/
|
||||||
|
private BigDecimal averageRiskScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 未解决高风险数量
|
||||||
|
*/
|
||||||
|
private Integer unresolvedHighCount;
|
||||||
|
}
|
||||||
146
src/main/java/cn/yinlihupo/domain/vo/RiskVO.java
Normal file
146
src/main/java/cn/yinlihupo/domain/vo/RiskVO.java
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
package cn.yinlihupo.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险VO
|
||||||
|
* 用于风险列表展示
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class RiskVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险编号
|
||||||
|
*/
|
||||||
|
private String riskCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目ID
|
||||||
|
*/
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目名称
|
||||||
|
*/
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险分类
|
||||||
|
*/
|
||||||
|
private String category;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险名称
|
||||||
|
*/
|
||||||
|
private String riskName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险来源
|
||||||
|
*/
|
||||||
|
private String riskSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发生概率(0-100%)
|
||||||
|
*/
|
||||||
|
private BigDecimal probability;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 影响程度(1-5)
|
||||||
|
*/
|
||||||
|
private BigDecimal impact;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险得分
|
||||||
|
*/
|
||||||
|
private BigDecimal riskScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险等级
|
||||||
|
*/
|
||||||
|
private String riskLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 负责人ID
|
||||||
|
*/
|
||||||
|
private Long ownerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 负责人姓名
|
||||||
|
*/
|
||||||
|
private String ownerName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 负责人头像
|
||||||
|
*/
|
||||||
|
private String ownerAvatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联工单数量
|
||||||
|
*/
|
||||||
|
private Integer workOrderCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓解措施
|
||||||
|
*/
|
||||||
|
private String mitigationPlan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应急计划
|
||||||
|
*/
|
||||||
|
private String contingencyPlan;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 触发条件
|
||||||
|
*/
|
||||||
|
private String triggerCondition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发现时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime discoverTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 预期解决日期
|
||||||
|
*/
|
||||||
|
private LocalDate dueDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解决时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime resolvedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签
|
||||||
|
*/
|
||||||
|
private List<String> tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package cn.yinlihupo.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单统计VO
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WorkOrderStatisticsVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单总数
|
||||||
|
*/
|
||||||
|
private Integer totalCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 待处理数量
|
||||||
|
*/
|
||||||
|
private Integer pendingCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已分配数量
|
||||||
|
*/
|
||||||
|
private Integer assignedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理中数量
|
||||||
|
*/
|
||||||
|
private Integer processingCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已完成数量
|
||||||
|
*/
|
||||||
|
private Integer completedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已关闭数量
|
||||||
|
*/
|
||||||
|
private Integer closedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 已驳回数量
|
||||||
|
*/
|
||||||
|
private Integer rejectedCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超期未完成数量
|
||||||
|
*/
|
||||||
|
private Integer overdueCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 即将超期数量(7天内)
|
||||||
|
*/
|
||||||
|
private Integer aboutToExpireCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按类型统计
|
||||||
|
*/
|
||||||
|
private Object typeStats;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按优先级统计
|
||||||
|
*/
|
||||||
|
private Object priorityStats;
|
||||||
|
}
|
||||||
154
src/main/java/cn/yinlihupo/domain/vo/WorkOrderVO.java
Normal file
154
src/main/java/cn/yinlihupo/domain/vo/WorkOrderVO.java
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package cn.yinlihupo.domain.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单VO
|
||||||
|
* 用于工单列表展示
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class WorkOrderVO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单编号
|
||||||
|
*/
|
||||||
|
private String orderCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单类型: bug-缺陷, feature-需求, task-任务, incident-事件, risk_handle-风险处理, other-其他
|
||||||
|
*/
|
||||||
|
private String orderType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目ID
|
||||||
|
*/
|
||||||
|
private Long projectId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 项目名称
|
||||||
|
*/
|
||||||
|
private String projectName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联风险ID
|
||||||
|
*/
|
||||||
|
private Long riskId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联风险名称
|
||||||
|
*/
|
||||||
|
private String riskName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单标题
|
||||||
|
*/
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人ID
|
||||||
|
*/
|
||||||
|
private Long creatorId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建人姓名
|
||||||
|
*/
|
||||||
|
private String creatorName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理人ID
|
||||||
|
*/
|
||||||
|
private Long handlerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理人姓名
|
||||||
|
*/
|
||||||
|
private String handlerName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理人头像
|
||||||
|
*/
|
||||||
|
private String handlerAvatar;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理组ID
|
||||||
|
*/
|
||||||
|
private Long handlerGroupId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优先级: critical-紧急, high-高, medium-中, low-低
|
||||||
|
*/
|
||||||
|
private String priority;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态: pending-待处理, assigned-已分派, processing-处理中, resolved-已解决, closed-已关闭, reopened-已重开
|
||||||
|
*/
|
||||||
|
private String status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 来源: web-网页, mobile-移动端, api-接口, system-系统生成, risk-风险分派
|
||||||
|
*/
|
||||||
|
private String source;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 截止时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime deadline;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分派时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime assignedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 首次响应时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime firstResponseTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解决时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime resolvedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime closedTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 满意度评分(1-5)
|
||||||
|
*/
|
||||||
|
private Integer satisfactionScore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否超期
|
||||||
|
*/
|
||||||
|
private Boolean isOverdue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标签
|
||||||
|
*/
|
||||||
|
private List<String> tags;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
57
src/main/java/cn/yinlihupo/mapper/WorkOrderMapper.java
Normal file
57
src/main/java/cn/yinlihupo/mapper/WorkOrderMapper.java
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package cn.yinlihupo.mapper;
|
||||||
|
|
||||||
|
import cn.yinlihupo.domain.entity.WorkOrder;
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单Mapper接口
|
||||||
|
*/
|
||||||
|
@Mapper
|
||||||
|
public interface WorkOrderMapper extends BaseMapper<WorkOrder> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询工单列表(含处理人信息,支持多条件筛选)
|
||||||
|
*/
|
||||||
|
List<Map<String, Object>> selectWorkOrderPageWithHandler(@Param("projectId") Long projectId,
|
||||||
|
@Param("handlerId") Long handlerId,
|
||||||
|
@Param("orderType") String orderType,
|
||||||
|
@Param("status") String status,
|
||||||
|
@Param("priority") String priority,
|
||||||
|
@Param("keyword") String keyword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询我的工单列表
|
||||||
|
*/
|
||||||
|
List<Map<String, Object>> selectMyWorkOrders(@Param("handlerId") Long handlerId,
|
||||||
|
@Param("status") String status,
|
||||||
|
@Param("orderType") String orderType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按状态统计工单数量
|
||||||
|
*/
|
||||||
|
Map<String, Object> countByStatus(@Param("projectId") Long projectId,
|
||||||
|
@Param("handlerId") Long handlerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按类型统计工单数量
|
||||||
|
*/
|
||||||
|
List<Map<String, Object>> countByType(@Param("projectId") Long projectId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询即将超期的工单(N天内)
|
||||||
|
*/
|
||||||
|
List<WorkOrder> selectAboutToExpire(@Param("projectId") Long projectId,
|
||||||
|
@Param("handlerId") Long handlerId,
|
||||||
|
@Param("days") int days);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询超期未完成的工单
|
||||||
|
*/
|
||||||
|
List<WorkOrder> selectOverdueWorkOrders(@Param("projectId") Long projectId,
|
||||||
|
@Param("handlerId") Long handlerId);
|
||||||
|
}
|
||||||
87
src/main/java/cn/yinlihupo/service/risk/RiskService.java
Normal file
87
src/main/java/cn/yinlihupo/service/risk/RiskService.java
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
package cn.yinlihupo.service.risk;
|
||||||
|
|
||||||
|
import cn.yinlihupo.common.page.TableDataInfo;
|
||||||
|
import cn.yinlihupo.domain.dto.CreateRiskRequest;
|
||||||
|
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
|
||||||
|
import cn.yinlihupo.domain.vo.RiskStatisticsVO;
|
||||||
|
import cn.yinlihupo.domain.vo.RiskVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险服务接口
|
||||||
|
*/
|
||||||
|
public interface RiskService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建风险评估
|
||||||
|
*
|
||||||
|
* @param request 创建请求
|
||||||
|
* @return 风险ID
|
||||||
|
*/
|
||||||
|
Long createRisk(CreateRiskRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新风险
|
||||||
|
*
|
||||||
|
* @param riskId 风险ID
|
||||||
|
* @param request 更新请求
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
Boolean updateRisk(Long riskId, CreateRiskRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除风险
|
||||||
|
*
|
||||||
|
* @param riskId 风险ID
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
Boolean deleteRisk(Long riskId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取风险详情
|
||||||
|
*
|
||||||
|
* @param riskId 风险ID
|
||||||
|
* @return 风险详情
|
||||||
|
*/
|
||||||
|
RiskVO getRiskDetail(Long riskId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询风险列表
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param pageNum 页码
|
||||||
|
* @param pageSize 每页大小
|
||||||
|
* @param category 分类筛选
|
||||||
|
* @param riskLevel 风险等级筛选
|
||||||
|
* @param status 状态筛选
|
||||||
|
* @param keyword 关键词搜索
|
||||||
|
* @return 分页风险列表
|
||||||
|
*/
|
||||||
|
TableDataInfo<RiskVO> getRiskList(Long projectId, Integer pageNum, Integer pageSize,
|
||||||
|
String category, String riskLevel, String status, String keyword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取风险统计信息
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @return 统计信息
|
||||||
|
*/
|
||||||
|
RiskStatisticsVO getRiskStatistics(Long projectId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为风险分配工单
|
||||||
|
*
|
||||||
|
* @param riskId 风险ID
|
||||||
|
* @param request 工单创建请求
|
||||||
|
* @return 工单ID
|
||||||
|
*/
|
||||||
|
Long assignWorkOrder(Long riskId, CreateWorkOrderRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量更新风险状态
|
||||||
|
*
|
||||||
|
* @param riskIds 风险ID列表
|
||||||
|
* @param status 新状态
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
Boolean batchUpdateStatus(java.util.List<Long> riskIds, String status);
|
||||||
|
}
|
||||||
105
src/main/java/cn/yinlihupo/service/risk/WorkOrderService.java
Normal file
105
src/main/java/cn/yinlihupo/service/risk/WorkOrderService.java
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
package cn.yinlihupo.service.risk;
|
||||||
|
|
||||||
|
import cn.yinlihupo.common.page.TableDataInfo;
|
||||||
|
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
|
||||||
|
import cn.yinlihupo.domain.dto.ProcessWorkOrderRequest;
|
||||||
|
import cn.yinlihupo.domain.vo.WorkOrderStatisticsVO;
|
||||||
|
import cn.yinlihupo.domain.vo.WorkOrderVO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单服务接口
|
||||||
|
*/
|
||||||
|
public interface WorkOrderService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建工单
|
||||||
|
*
|
||||||
|
* @param request 创建请求
|
||||||
|
* @return 工单ID
|
||||||
|
*/
|
||||||
|
Long createWorkOrder(CreateWorkOrderRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新工单
|
||||||
|
*
|
||||||
|
* @param workOrderId 工单ID
|
||||||
|
* @param request 更新请求
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
Boolean updateWorkOrder(Long workOrderId, CreateWorkOrderRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除工单
|
||||||
|
*
|
||||||
|
* @param workOrderId 工单ID
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
Boolean deleteWorkOrder(Long workOrderId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工单详情
|
||||||
|
*
|
||||||
|
* @param workOrderId 工单ID
|
||||||
|
* @return 工单详情
|
||||||
|
*/
|
||||||
|
WorkOrderVO getWorkOrderDetail(Long workOrderId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询工单列表
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID
|
||||||
|
* @param pageNum 页码
|
||||||
|
* @param pageSize 每页大小
|
||||||
|
* @param orderType 类型筛选
|
||||||
|
* @param status 状态筛选
|
||||||
|
* @param priority 优先级筛选
|
||||||
|
* @param keyword 关键词搜索
|
||||||
|
* @return 分页工单列表
|
||||||
|
*/
|
||||||
|
TableDataInfo<WorkOrderVO> getWorkOrderList(Long projectId, Integer pageNum, Integer pageSize,
|
||||||
|
String orderType, String status, String priority, String keyword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的工单列表
|
||||||
|
*
|
||||||
|
* @param pageNum 页码
|
||||||
|
* @param pageSize 每页大小
|
||||||
|
* @param status 状态筛选
|
||||||
|
* @param orderType 类型筛选
|
||||||
|
* @return 分页工单列表
|
||||||
|
*/
|
||||||
|
TableDataInfo<WorkOrderVO> getMyWorkOrders(Integer pageNum, Integer pageSize,
|
||||||
|
String status, String orderType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理工单
|
||||||
|
*
|
||||||
|
* @param request 处理请求
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
Boolean processWorkOrder(ProcessWorkOrderRequest request);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分配工单给处理人
|
||||||
|
*
|
||||||
|
* @param workOrderId 工单ID
|
||||||
|
* @param handlerId 处理人ID
|
||||||
|
* @return 是否成功
|
||||||
|
*/
|
||||||
|
Boolean assignWorkOrder(Long workOrderId, Long handlerId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取工单统计信息
|
||||||
|
*
|
||||||
|
* @param projectId 项目ID(可选)
|
||||||
|
* @return 统计信息
|
||||||
|
*/
|
||||||
|
WorkOrderStatisticsVO getWorkOrderStatistics(Long projectId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取我的工单统计信息
|
||||||
|
*
|
||||||
|
* @return 统计信息
|
||||||
|
*/
|
||||||
|
WorkOrderStatisticsVO getMyWorkOrderStatistics();
|
||||||
|
}
|
||||||
@@ -0,0 +1,416 @@
|
|||||||
|
package cn.yinlihupo.service.risk.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import cn.yinlihupo.common.page.TableDataInfo;
|
||||||
|
import cn.yinlihupo.common.util.SecurityUtils;
|
||||||
|
import cn.yinlihupo.domain.dto.CreateRiskRequest;
|
||||||
|
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
|
||||||
|
import cn.yinlihupo.domain.entity.Project;
|
||||||
|
import cn.yinlihupo.domain.entity.Risk;
|
||||||
|
import cn.yinlihupo.domain.entity.SysUser;
|
||||||
|
import cn.yinlihupo.domain.entity.WorkOrder;
|
||||||
|
import cn.yinlihupo.domain.vo.RiskStatisticsVO;
|
||||||
|
import cn.yinlihupo.domain.vo.RiskVO;
|
||||||
|
import cn.yinlihupo.mapper.ProjectMapper;
|
||||||
|
import cn.yinlihupo.mapper.RiskMapper;
|
||||||
|
import cn.yinlihupo.mapper.SysUserMapper;
|
||||||
|
import cn.yinlihupo.mapper.WorkOrderMapper;
|
||||||
|
import cn.yinlihupo.service.risk.RiskService;
|
||||||
|
import cn.yinlihupo.service.risk.WorkOrderService;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 风险服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class RiskServiceImpl implements RiskService {
|
||||||
|
|
||||||
|
private final RiskMapper riskMapper;
|
||||||
|
private final ProjectMapper projectMapper;
|
||||||
|
private final SysUserMapper sysUserMapper;
|
||||||
|
private final WorkOrderMapper workOrderMapper;
|
||||||
|
private final WorkOrderService workOrderService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Long createRisk(CreateRiskRequest request) {
|
||||||
|
log.info("创建风险评估, projectId: {}, riskName: {}", request.getProjectId(), request.getRiskName());
|
||||||
|
|
||||||
|
// 验证项目存在
|
||||||
|
Project project = projectMapper.selectById(request.getProjectId());
|
||||||
|
if (project == null || project.getDeleted() == 1) {
|
||||||
|
throw new RuntimeException("项目不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
Risk risk = new Risk();
|
||||||
|
risk.setProjectId(request.getProjectId());
|
||||||
|
risk.setRiskCode(generateRiskCode());
|
||||||
|
risk.setCategory(request.getCategory());
|
||||||
|
risk.setRiskName(request.getRiskName());
|
||||||
|
risk.setDescription(request.getDescription());
|
||||||
|
risk.setRiskSource(request.getRiskSource() != null ? request.getRiskSource() : "manual");
|
||||||
|
|
||||||
|
// 计算风险得分和等级
|
||||||
|
if (request.getProbability() != null && request.getImpact() != null) {
|
||||||
|
risk.setProbability(BigDecimal.valueOf(request.getProbability()));
|
||||||
|
risk.setImpact(BigDecimal.valueOf(request.getImpact()));
|
||||||
|
BigDecimal score = risk.getProbability()
|
||||||
|
.multiply(risk.getImpact())
|
||||||
|
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
||||||
|
risk.setRiskScore(score);
|
||||||
|
risk.setRiskLevel(calculateRiskLevel(request.getProbability(), request.getImpact()));
|
||||||
|
}
|
||||||
|
|
||||||
|
risk.setOwnerId(request.getOwnerId());
|
||||||
|
risk.setMitigationPlan(request.getMitigationPlan());
|
||||||
|
risk.setContingencyPlan(request.getContingencyPlan());
|
||||||
|
risk.setTriggerCondition(request.getTriggerCondition());
|
||||||
|
risk.setDueDate(request.getDueDate());
|
||||||
|
risk.setTags(request.getTags());
|
||||||
|
risk.setStatus("identified");
|
||||||
|
risk.setDiscoverTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
riskMapper.insert(risk);
|
||||||
|
log.info("风险评估创建成功, riskId: {}", risk.getId());
|
||||||
|
|
||||||
|
return risk.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean updateRisk(Long riskId, CreateRiskRequest request) {
|
||||||
|
log.info("更新风险, riskId: {}", riskId);
|
||||||
|
|
||||||
|
Risk risk = riskMapper.selectById(riskId);
|
||||||
|
if (risk == null || risk.getDeleted() == 1) {
|
||||||
|
throw new RuntimeException("风险不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.getCategory() != null) {
|
||||||
|
risk.setCategory(request.getCategory());
|
||||||
|
}
|
||||||
|
if (request.getRiskName() != null) {
|
||||||
|
risk.setRiskName(request.getRiskName());
|
||||||
|
}
|
||||||
|
if (request.getDescription() != null) {
|
||||||
|
risk.setDescription(request.getDescription());
|
||||||
|
}
|
||||||
|
if (request.getProbability() != null && request.getImpact() != null) {
|
||||||
|
risk.setProbability(BigDecimal.valueOf(request.getProbability()));
|
||||||
|
risk.setImpact(BigDecimal.valueOf(request.getImpact()));
|
||||||
|
BigDecimal score = risk.getProbability()
|
||||||
|
.multiply(risk.getImpact())
|
||||||
|
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
||||||
|
risk.setRiskScore(score);
|
||||||
|
risk.setRiskLevel(calculateRiskLevel(request.getProbability(), request.getImpact()));
|
||||||
|
}
|
||||||
|
if (request.getOwnerId() != null) {
|
||||||
|
risk.setOwnerId(request.getOwnerId());
|
||||||
|
}
|
||||||
|
if (request.getMitigationPlan() != null) {
|
||||||
|
risk.setMitigationPlan(request.getMitigationPlan());
|
||||||
|
}
|
||||||
|
if (request.getContingencyPlan() != null) {
|
||||||
|
risk.setContingencyPlan(request.getContingencyPlan());
|
||||||
|
}
|
||||||
|
if (request.getTriggerCondition() != null) {
|
||||||
|
risk.setTriggerCondition(request.getTriggerCondition());
|
||||||
|
}
|
||||||
|
if (request.getDueDate() != null) {
|
||||||
|
risk.setDueDate(request.getDueDate());
|
||||||
|
}
|
||||||
|
if (request.getTags() != null) {
|
||||||
|
risk.setTags(request.getTags());
|
||||||
|
}
|
||||||
|
|
||||||
|
riskMapper.updateById(risk);
|
||||||
|
log.info("风险更新成功, riskId: {}", riskId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean deleteRisk(Long riskId) {
|
||||||
|
log.info("删除风险, riskId: {}", riskId);
|
||||||
|
|
||||||
|
Risk risk = riskMapper.selectById(riskId);
|
||||||
|
if (risk == null || risk.getDeleted() == 1) {
|
||||||
|
throw new RuntimeException("风险不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
riskMapper.deleteById(riskId);
|
||||||
|
log.info("风险删除成功, riskId: {}", riskId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RiskVO getRiskDetail(Long riskId) {
|
||||||
|
log.info("获取风险详情, riskId: {}", riskId);
|
||||||
|
|
||||||
|
Risk risk = riskMapper.selectById(riskId);
|
||||||
|
if (risk == null || risk.getDeleted() == 1) {
|
||||||
|
throw new RuntimeException("风险不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertToRiskVO(risk);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<RiskVO> getRiskList(Long projectId, Integer pageNum, Integer pageSize,
|
||||||
|
String category, String riskLevel, String status, String keyword) {
|
||||||
|
log.info("分页查询风险列表, projectId: {}, pageNum: {}, pageSize: {}", projectId, pageNum, pageSize);
|
||||||
|
|
||||||
|
Page<Risk> page = new Page<>(pageNum, pageSize);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<Risk> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(Risk::getDeleted, 0);
|
||||||
|
|
||||||
|
if (projectId != null) {
|
||||||
|
wrapper.eq(Risk::getProjectId, projectId);
|
||||||
|
}
|
||||||
|
if (category != null && !category.isEmpty()) {
|
||||||
|
wrapper.eq(Risk::getCategory, category);
|
||||||
|
}
|
||||||
|
if (riskLevel != null && !riskLevel.isEmpty()) {
|
||||||
|
wrapper.eq(Risk::getRiskLevel, riskLevel);
|
||||||
|
}
|
||||||
|
if (status != null && !status.isEmpty()) {
|
||||||
|
wrapper.eq(Risk::getStatus, status);
|
||||||
|
}
|
||||||
|
if (keyword != null && !keyword.isEmpty()) {
|
||||||
|
wrapper.and(w -> w.like(Risk::getRiskName, keyword)
|
||||||
|
.or()
|
||||||
|
.like(Risk::getDescription, keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按风险等级和得分排序
|
||||||
|
wrapper.orderByAsc(
|
||||||
|
r -> "critical".equals(r.getRiskLevel()) ? 1 :
|
||||||
|
"high".equals(r.getRiskLevel()) ? 2 :
|
||||||
|
"medium".equals(r.getRiskLevel()) ? 3 : 4
|
||||||
|
).orderByDesc(Risk::getRiskScore);
|
||||||
|
|
||||||
|
Page<Risk> riskPage = riskMapper.selectPage(page, wrapper);
|
||||||
|
|
||||||
|
List<RiskVO> voList = riskPage.getRecords().stream()
|
||||||
|
.map(this::convertToRiskVO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return TableDataInfo.build(new Page<RiskVO>(riskPage.getCurrent(), riskPage.getSize(), riskPage.getTotal())
|
||||||
|
.setRecords(voList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RiskStatisticsVO getRiskStatistics(Long projectId) {
|
||||||
|
log.info("获取风险统计信息, projectId: {}", projectId);
|
||||||
|
|
||||||
|
RiskStatisticsVO statistics = new RiskStatisticsVO();
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
LambdaQueryWrapper<Risk> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(Risk::getDeleted, 0);
|
||||||
|
if (projectId != null) {
|
||||||
|
wrapper.eq(Risk::getProjectId, projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Risk> risks = riskMapper.selectList(wrapper);
|
||||||
|
|
||||||
|
// 统计总数
|
||||||
|
statistics.setTotalCount(risks.size());
|
||||||
|
|
||||||
|
// 按状态统计
|
||||||
|
Map<String, Long> statusCount = risks.stream()
|
||||||
|
.collect(Collectors.groupingBy(Risk::getStatus, Collectors.counting()));
|
||||||
|
statistics.setIdentifiedCount(statusCount.getOrDefault("identified", 0L).intValue());
|
||||||
|
statistics.setAssignedCount(statusCount.getOrDefault("assigned", 0L).intValue());
|
||||||
|
statistics.setMitigatingCount(statusCount.getOrDefault("mitigating", 0L).intValue());
|
||||||
|
statistics.setResolvedCount(statusCount.getOrDefault("resolved", 0L).intValue());
|
||||||
|
statistics.setClosedCount(statusCount.getOrDefault("closed", 0L).intValue());
|
||||||
|
|
||||||
|
// 按等级统计
|
||||||
|
Map<String, Long> levelCount = risks.stream()
|
||||||
|
.collect(Collectors.groupingBy(Risk::getRiskLevel, Collectors.counting()));
|
||||||
|
statistics.setCriticalCount(levelCount.getOrDefault("critical", 0L).intValue());
|
||||||
|
statistics.setHighCount(levelCount.getOrDefault("high", 0L).intValue());
|
||||||
|
statistics.setMediumCount(levelCount.getOrDefault("medium", 0L).intValue());
|
||||||
|
statistics.setLowCount(levelCount.getOrDefault("low", 0L).intValue());
|
||||||
|
|
||||||
|
// 计算平均风险得分
|
||||||
|
double avgScore = risks.stream()
|
||||||
|
.filter(r -> r.getRiskScore() != null)
|
||||||
|
.mapToDouble(r -> r.getRiskScore().doubleValue())
|
||||||
|
.average()
|
||||||
|
.orElse(0.0);
|
||||||
|
statistics.setAverageRiskScore(BigDecimal.valueOf(avgScore).setScale(2, RoundingMode.HALF_UP));
|
||||||
|
|
||||||
|
// 统计未解决的高风险数量
|
||||||
|
int unresolvedHigh = (int) risks.stream()
|
||||||
|
.filter(r -> !"resolved".equals(r.getStatus()) && !"closed".equals(r.getStatus()))
|
||||||
|
.filter(r -> "critical".equals(r.getRiskLevel()) || "high".equals(r.getRiskLevel()))
|
||||||
|
.count();
|
||||||
|
statistics.setUnresolvedHighCount(unresolvedHigh);
|
||||||
|
|
||||||
|
// 按分类统计
|
||||||
|
Map<String, Long> categoryStats = risks.stream()
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
r -> r.getCategory() != null ? r.getCategory() : "other",
|
||||||
|
Collectors.counting()
|
||||||
|
));
|
||||||
|
statistics.setCategoryStats(categoryStats);
|
||||||
|
|
||||||
|
// 按等级统计
|
||||||
|
statistics.setLevelStats(levelCount);
|
||||||
|
|
||||||
|
return statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Long assignWorkOrder(Long riskId, CreateWorkOrderRequest request) {
|
||||||
|
log.info("为风险分配工单, riskId: {}", riskId);
|
||||||
|
|
||||||
|
Risk risk = riskMapper.selectById(riskId);
|
||||||
|
if (risk == null || risk.getDeleted() == 1) {
|
||||||
|
throw new RuntimeException("风险不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置工单关联
|
||||||
|
request.setRiskId(riskId);
|
||||||
|
request.setProjectId(risk.getProjectId());
|
||||||
|
request.setOrderType("risk_handle");
|
||||||
|
request.setSource("risk");
|
||||||
|
|
||||||
|
// 创建工单
|
||||||
|
Long workOrderId = workOrderService.createWorkOrder(request);
|
||||||
|
|
||||||
|
// 更新风险的工单关联
|
||||||
|
List<Long> workOrderIds = risk.getWorkOrderIds();
|
||||||
|
if (workOrderIds == null) {
|
||||||
|
workOrderIds = new ArrayList<>();
|
||||||
|
}
|
||||||
|
workOrderIds.add(workOrderId);
|
||||||
|
risk.setWorkOrderIds(workOrderIds);
|
||||||
|
risk.setStatus("assigned");
|
||||||
|
riskMapper.updateById(risk);
|
||||||
|
|
||||||
|
log.info("风险工单分配成功, riskId: {}, workOrderId: {}", riskId, workOrderId);
|
||||||
|
return workOrderId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean batchUpdateStatus(List<Long> riskIds, String status) {
|
||||||
|
log.info("批量更新风险状态, riskIds: {}, status: {}", riskIds, status);
|
||||||
|
|
||||||
|
for (Long riskId : riskIds) {
|
||||||
|
Risk risk = riskMapper.selectById(riskId);
|
||||||
|
if (risk != null && risk.getDeleted() == 0) {
|
||||||
|
risk.setStatus(status);
|
||||||
|
if ("resolved".equals(status)) {
|
||||||
|
risk.setResolvedTime(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
riskMapper.updateById(risk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("批量更新风险状态完成, 更新数量: {}", riskIds.size());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成风险编号
|
||||||
|
*/
|
||||||
|
private String generateRiskCode() {
|
||||||
|
return "RSK" + IdUtil.fastSimpleUUID().substring(0, 12).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 计算风险等级
|
||||||
|
*/
|
||||||
|
private String calculateRiskLevel(Integer probability, Integer impact) {
|
||||||
|
if (probability == null || impact == null) {
|
||||||
|
return "low";
|
||||||
|
}
|
||||||
|
int score = probability * impact;
|
||||||
|
if (score >= 300) {
|
||||||
|
return "critical";
|
||||||
|
} else if (score >= 200) {
|
||||||
|
return "high";
|
||||||
|
} else if (score >= 100) {
|
||||||
|
return "medium";
|
||||||
|
} else {
|
||||||
|
return "low";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为VO
|
||||||
|
*/
|
||||||
|
private RiskVO convertToRiskVO(Risk risk) {
|
||||||
|
RiskVO vo = new RiskVO();
|
||||||
|
vo.setId(risk.getId());
|
||||||
|
vo.setRiskCode(risk.getRiskCode());
|
||||||
|
vo.setProjectId(risk.getProjectId());
|
||||||
|
vo.setCategory(risk.getCategory());
|
||||||
|
vo.setRiskName(risk.getRiskName());
|
||||||
|
vo.setDescription(risk.getDescription());
|
||||||
|
vo.setRiskSource(risk.getRiskSource());
|
||||||
|
vo.setProbability(risk.getProbability());
|
||||||
|
vo.setImpact(risk.getImpact());
|
||||||
|
vo.setRiskScore(risk.getRiskScore());
|
||||||
|
vo.setRiskLevel(risk.getRiskLevel());
|
||||||
|
vo.setStatus(risk.getStatus());
|
||||||
|
vo.setOwnerId(risk.getOwnerId());
|
||||||
|
vo.setMitigationPlan(risk.getMitigationPlan());
|
||||||
|
vo.setContingencyPlan(risk.getContingencyPlan());
|
||||||
|
vo.setTriggerCondition(risk.getTriggerCondition());
|
||||||
|
vo.setDiscoverTime(risk.getDiscoverTime());
|
||||||
|
vo.setDueDate(risk.getDueDate());
|
||||||
|
vo.setResolvedTime(risk.getResolvedTime());
|
||||||
|
vo.setTags(risk.getTags());
|
||||||
|
vo.setCreateTime(risk.getCreateTime());
|
||||||
|
vo.setUpdateTime(risk.getUpdateTime());
|
||||||
|
|
||||||
|
// 查询项目名称
|
||||||
|
if (risk.getProjectId() != null) {
|
||||||
|
Project project = projectMapper.selectById(risk.getProjectId());
|
||||||
|
if (project != null) {
|
||||||
|
vo.setProjectName(project.getProjectName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询负责人信息
|
||||||
|
if (risk.getOwnerId() != null) {
|
||||||
|
SysUser owner = sysUserMapper.selectById(risk.getOwnerId());
|
||||||
|
if (owner != null) {
|
||||||
|
vo.setOwnerName(owner.getRealName());
|
||||||
|
vo.setOwnerAvatar(owner.getAvatar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算关联工单数量
|
||||||
|
if (risk.getWorkOrderIds() != null) {
|
||||||
|
vo.setWorkOrderCount(risk.getWorkOrderIds().size());
|
||||||
|
} else {
|
||||||
|
vo.setWorkOrderCount(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,515 @@
|
|||||||
|
package cn.yinlihupo.service.risk.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.IdUtil;
|
||||||
|
import cn.yinlihupo.common.page.TableDataInfo;
|
||||||
|
import cn.yinlihupo.common.util.SecurityUtils;
|
||||||
|
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
|
||||||
|
import cn.yinlihupo.domain.dto.ProcessWorkOrderRequest;
|
||||||
|
import cn.yinlihupo.domain.entity.*;
|
||||||
|
import cn.yinlihupo.domain.vo.WorkOrderStatisticsVO;
|
||||||
|
import cn.yinlihupo.domain.vo.WorkOrderVO;
|
||||||
|
import cn.yinlihupo.mapper.*;
|
||||||
|
import cn.yinlihupo.service.risk.WorkOrderService;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 工单服务实现类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WorkOrderServiceImpl implements WorkOrderService {
|
||||||
|
|
||||||
|
private final WorkOrderMapper workOrderMapper;
|
||||||
|
private final ProjectMapper projectMapper;
|
||||||
|
private final RiskMapper riskMapper;
|
||||||
|
private final SysUserMapper sysUserMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Long createWorkOrder(CreateWorkOrderRequest request) {
|
||||||
|
log.info("创建工单, projectId: {}, title: {}", request.getProjectId(), request.getTitle());
|
||||||
|
|
||||||
|
// 验证项目存在
|
||||||
|
Project project = projectMapper.selectById(request.getProjectId());
|
||||||
|
if (project == null || project.getDeleted() == 1) {
|
||||||
|
throw new RuntimeException("项目不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkOrder workOrder = new WorkOrder();
|
||||||
|
workOrder.setProjectId(request.getProjectId());
|
||||||
|
workOrder.setOrderCode(generateOrderCode());
|
||||||
|
workOrder.setRiskId(request.getRiskId());
|
||||||
|
workOrder.setOrderType(request.getOrderType() != null ? request.getOrderType() : "other");
|
||||||
|
workOrder.setTitle(request.getTitle());
|
||||||
|
workOrder.setDescription(request.getDescription());
|
||||||
|
workOrder.setPriority(request.getPriority() != null ? request.getPriority() : "medium");
|
||||||
|
workOrder.setHandlerId(request.getHandlerId());
|
||||||
|
workOrder.setHandlerGroupId(request.getHandlerGroupId());
|
||||||
|
workOrder.setDeadline(request.getDeadline());
|
||||||
|
workOrder.setSource(request.getSource() != null ? request.getSource() : "api");
|
||||||
|
workOrder.setTags(request.getTags());
|
||||||
|
|
||||||
|
// 设置创建人和状态
|
||||||
|
workOrder.setCreatorId(SecurityUtils.getCurrentUserId());
|
||||||
|
if (request.getHandlerId() != null) {
|
||||||
|
workOrder.setStatus("assigned");
|
||||||
|
workOrder.setAssignedTime(LocalDateTime.now());
|
||||||
|
} else {
|
||||||
|
workOrder.setStatus("pending");
|
||||||
|
}
|
||||||
|
|
||||||
|
workOrderMapper.insert(workOrder);
|
||||||
|
log.info("工单创建成功, workOrderId: {}", workOrder.getId());
|
||||||
|
|
||||||
|
return workOrder.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean updateWorkOrder(Long workOrderId, CreateWorkOrderRequest request) {
|
||||||
|
log.info("更新工单, workOrderId: {}", workOrderId);
|
||||||
|
|
||||||
|
WorkOrder workOrder = workOrderMapper.selectById(workOrderId);
|
||||||
|
if (workOrder == null || workOrder.getDeleted() == 1) {
|
||||||
|
throw new RuntimeException("工单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.getTitle() != null) {
|
||||||
|
workOrder.setTitle(request.getTitle());
|
||||||
|
}
|
||||||
|
if (request.getDescription() != null) {
|
||||||
|
workOrder.setDescription(request.getDescription());
|
||||||
|
}
|
||||||
|
if (request.getPriority() != null) {
|
||||||
|
workOrder.setPriority(request.getPriority());
|
||||||
|
}
|
||||||
|
if (request.getHandlerId() != null) {
|
||||||
|
workOrder.setHandlerId(request.getHandlerId());
|
||||||
|
if ("pending".equals(workOrder.getStatus())) {
|
||||||
|
workOrder.setStatus("assigned");
|
||||||
|
workOrder.setAssignedTime(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (request.getHandlerGroupId() != null) {
|
||||||
|
workOrder.setHandlerGroupId(request.getHandlerGroupId());
|
||||||
|
}
|
||||||
|
if (request.getDeadline() != null) {
|
||||||
|
workOrder.setDeadline(request.getDeadline());
|
||||||
|
}
|
||||||
|
if (request.getTags() != null) {
|
||||||
|
workOrder.setTags(request.getTags());
|
||||||
|
}
|
||||||
|
|
||||||
|
workOrderMapper.updateById(workOrder);
|
||||||
|
log.info("工单更新成功, workOrderId: {}", workOrderId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean deleteWorkOrder(Long workOrderId) {
|
||||||
|
log.info("删除工单, workOrderId: {}", workOrderId);
|
||||||
|
|
||||||
|
WorkOrder workOrder = workOrderMapper.selectById(workOrderId);
|
||||||
|
if (workOrder == null || workOrder.getDeleted() == 1) {
|
||||||
|
throw new RuntimeException("工单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
workOrderMapper.deleteById(workOrderId);
|
||||||
|
log.info("工单删除成功, workOrderId: {}", workOrderId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WorkOrderVO getWorkOrderDetail(Long workOrderId) {
|
||||||
|
log.info("获取工单详情, workOrderId: {}", workOrderId);
|
||||||
|
|
||||||
|
WorkOrder workOrder = workOrderMapper.selectById(workOrderId);
|
||||||
|
if (workOrder == null || workOrder.getDeleted() == 1) {
|
||||||
|
throw new RuntimeException("工单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertToWorkOrderVO(workOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<WorkOrderVO> getWorkOrderList(Long projectId, Integer pageNum, Integer pageSize,
|
||||||
|
String orderType, String status, String priority, String keyword) {
|
||||||
|
log.info("分页查询工单列表, projectId: {}, pageNum: {}, pageSize: {}", projectId, pageNum, pageSize);
|
||||||
|
|
||||||
|
Page<WorkOrder> page = new Page<>(pageNum, pageSize);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(WorkOrder::getDeleted, 0);
|
||||||
|
|
||||||
|
if (projectId != null) {
|
||||||
|
wrapper.eq(WorkOrder::getProjectId, projectId);
|
||||||
|
}
|
||||||
|
if (orderType != null && !orderType.isEmpty()) {
|
||||||
|
wrapper.eq(WorkOrder::getOrderType, orderType);
|
||||||
|
}
|
||||||
|
if (status != null && !status.isEmpty()) {
|
||||||
|
wrapper.eq(WorkOrder::getStatus, status);
|
||||||
|
}
|
||||||
|
if (priority != null && !priority.isEmpty()) {
|
||||||
|
wrapper.eq(WorkOrder::getPriority, priority);
|
||||||
|
}
|
||||||
|
if (keyword != null && !keyword.isEmpty()) {
|
||||||
|
wrapper.and(w -> w.like(WorkOrder::getTitle, keyword)
|
||||||
|
.or()
|
||||||
|
.like(WorkOrder::getDescription, keyword));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按优先级和创建时间排序
|
||||||
|
wrapper.orderByAsc(
|
||||||
|
w -> "critical".equals(w.getPriority()) ? 1 :
|
||||||
|
"high".equals(w.getPriority()) ? 2 :
|
||||||
|
"medium".equals(w.getPriority()) ? 3 : 4
|
||||||
|
).orderByDesc(WorkOrder::getCreateTime);
|
||||||
|
|
||||||
|
Page<WorkOrder> orderPage = workOrderMapper.selectPage(page, wrapper);
|
||||||
|
|
||||||
|
List<WorkOrderVO> voList = orderPage.getRecords().stream()
|
||||||
|
.map(this::convertToWorkOrderVO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return TableDataInfo.build(new Page<WorkOrderVO>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal())
|
||||||
|
.setRecords(voList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<WorkOrderVO> getMyWorkOrders(Integer pageNum, Integer pageSize,
|
||||||
|
String status, String orderType) {
|
||||||
|
Long userId = SecurityUtils.getCurrentUserId();
|
||||||
|
log.info("查询我的工单列表, userId: {}, pageNum: {}, pageSize: {}", userId, pageNum, pageSize);
|
||||||
|
|
||||||
|
Page<WorkOrder> page = new Page<>(pageNum, pageSize);
|
||||||
|
|
||||||
|
LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(WorkOrder::getDeleted, 0)
|
||||||
|
.eq(WorkOrder::getHandlerId, userId);
|
||||||
|
|
||||||
|
if (status != null && !status.isEmpty()) {
|
||||||
|
wrapper.eq(WorkOrder::getStatus, status);
|
||||||
|
}
|
||||||
|
if (orderType != null && !orderType.isEmpty()) {
|
||||||
|
wrapper.eq(WorkOrder::getOrderType, orderType);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按优先级和创建时间排序
|
||||||
|
wrapper.orderByAsc(
|
||||||
|
w -> "critical".equals(w.getPriority()) ? 1 :
|
||||||
|
"high".equals(w.getPriority()) ? 2 :
|
||||||
|
"medium".equals(w.getPriority()) ? 3 : 4
|
||||||
|
).orderByDesc(WorkOrder::getCreateTime);
|
||||||
|
|
||||||
|
Page<WorkOrder> orderPage = workOrderMapper.selectPage(page, wrapper);
|
||||||
|
|
||||||
|
List<WorkOrderVO> voList = orderPage.getRecords().stream()
|
||||||
|
.map(this::convertToWorkOrderVO)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
return TableDataInfo.build(new Page<WorkOrderVO>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal())
|
||||||
|
.setRecords(voList));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean processWorkOrder(ProcessWorkOrderRequest request) {
|
||||||
|
Long userId = SecurityUtils.getCurrentUserId();
|
||||||
|
log.info("处理工单, workOrderId: {}, userId: {}", request.getWorkOrderId(), userId);
|
||||||
|
|
||||||
|
WorkOrder workOrder = workOrderMapper.selectById(request.getWorkOrderId());
|
||||||
|
if (workOrder == null || workOrder.getDeleted() == 1) {
|
||||||
|
throw new RuntimeException("工单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证是否为处理人
|
||||||
|
if (!userId.equals(workOrder.getHandlerId())) {
|
||||||
|
throw new RuntimeException("您不是该工单的处理人");
|
||||||
|
}
|
||||||
|
|
||||||
|
String oldStatus = workOrder.getStatus();
|
||||||
|
String newStatus = request.getStatus();
|
||||||
|
|
||||||
|
// 更新状态
|
||||||
|
if (newStatus != null) {
|
||||||
|
workOrder.setStatus(newStatus);
|
||||||
|
|
||||||
|
// 记录首次响应时间
|
||||||
|
if ("processing".equals(newStatus) && workOrder.getFirstResponseTime() == null) {
|
||||||
|
workOrder.setFirstResponseTime(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录解决时间
|
||||||
|
if ("resolved".equals(newStatus)) {
|
||||||
|
workOrder.setResolvedTime(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录关闭时间
|
||||||
|
if ("closed".equals(newStatus)) {
|
||||||
|
workOrder.setClosedTime(LocalDateTime.now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新满意度评分
|
||||||
|
if (request.getSatisfactionScore() != null) {
|
||||||
|
workOrder.setSatisfactionScore(request.getSatisfactionScore());
|
||||||
|
}
|
||||||
|
|
||||||
|
workOrderMapper.updateById(workOrder);
|
||||||
|
|
||||||
|
// 如果是风险工单,更新风险状态
|
||||||
|
if ("resolved".equals(newStatus) && workOrder.getRiskId() != null) {
|
||||||
|
Risk risk = riskMapper.selectById(workOrder.getRiskId());
|
||||||
|
if (risk != null && "assigned".equals(risk.getStatus())) {
|
||||||
|
risk.setStatus("mitigating");
|
||||||
|
riskMapper.updateById(risk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("工单处理成功, workOrderId: {}, status: {} -> {}", request.getWorkOrderId(), oldStatus, newStatus);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Boolean assignWorkOrder(Long workOrderId, Long handlerId) {
|
||||||
|
log.info("分配工单, workOrderId: {}, handlerId: {}", workOrderId, handlerId);
|
||||||
|
|
||||||
|
WorkOrder workOrder = workOrderMapper.selectById(workOrderId);
|
||||||
|
if (workOrder == null || workOrder.getDeleted() == 1) {
|
||||||
|
throw new RuntimeException("工单不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
workOrder.setHandlerId(handlerId);
|
||||||
|
workOrder.setStatus("assigned");
|
||||||
|
workOrder.setAssignedTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
workOrderMapper.updateById(workOrder);
|
||||||
|
log.info("工单分配成功, workOrderId: {}", workOrderId);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WorkOrderStatisticsVO getWorkOrderStatistics(Long projectId) {
|
||||||
|
log.info("获取工单统计信息, projectId: {}", projectId);
|
||||||
|
|
||||||
|
WorkOrderStatisticsVO statistics = new WorkOrderStatisticsVO();
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(WorkOrder::getDeleted, 0);
|
||||||
|
if (projectId != null) {
|
||||||
|
wrapper.eq(WorkOrder::getProjectId, projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<WorkOrder> orders = workOrderMapper.selectList(wrapper);
|
||||||
|
|
||||||
|
// 统计总数
|
||||||
|
statistics.setTotalCount(orders.size());
|
||||||
|
|
||||||
|
// 按状态统计
|
||||||
|
Map<String, Long> statusCount = orders.stream()
|
||||||
|
.collect(Collectors.groupingBy(WorkOrder::getStatus, Collectors.counting()));
|
||||||
|
statistics.setPendingCount(statusCount.getOrDefault("pending", 0L).intValue());
|
||||||
|
statistics.setAssignedCount(statusCount.getOrDefault("assigned", 0L).intValue());
|
||||||
|
statistics.setProcessingCount(statusCount.getOrDefault("processing", 0L).intValue());
|
||||||
|
statistics.setCompletedCount(statusCount.getOrDefault("resolved", 0L).intValue());
|
||||||
|
statistics.setClosedCount(statusCount.getOrDefault("closed", 0L).intValue());
|
||||||
|
statistics.setRejectedCount(0); // 新结构没有 rejected 状态
|
||||||
|
|
||||||
|
// 统计超期未完成
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
int overdueCount = (int) orders.stream()
|
||||||
|
.filter(o -> !"resolved".equals(o.getStatus()) && !"closed".equals(o.getStatus()))
|
||||||
|
.filter(o -> o.getDeadline() != null && o.getDeadline().isBefore(now))
|
||||||
|
.count();
|
||||||
|
statistics.setOverdueCount(overdueCount);
|
||||||
|
|
||||||
|
// 统计即将超期(7天内)
|
||||||
|
LocalDateTime sevenDaysLater = now.plusDays(7);
|
||||||
|
int aboutToExpireCount = (int) orders.stream()
|
||||||
|
.filter(o -> !"resolved".equals(o.getStatus()) && !"closed".equals(o.getStatus()))
|
||||||
|
.filter(o -> o.getDeadline() != null
|
||||||
|
&& !o.getDeadline().isBefore(now)
|
||||||
|
&& !o.getDeadline().isAfter(sevenDaysLater))
|
||||||
|
.count();
|
||||||
|
statistics.setAboutToExpireCount(aboutToExpireCount);
|
||||||
|
|
||||||
|
// 按类型统计
|
||||||
|
Map<String, Long> typeStats = orders.stream()
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
o -> o.getOrderType() != null ? o.getOrderType() : "other",
|
||||||
|
Collectors.counting()
|
||||||
|
));
|
||||||
|
statistics.setTypeStats(typeStats);
|
||||||
|
|
||||||
|
// 按优先级统计
|
||||||
|
Map<String, Long> priorityStats = orders.stream()
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
o -> o.getPriority() != null ? o.getPriority() : "medium",
|
||||||
|
Collectors.counting()
|
||||||
|
));
|
||||||
|
statistics.setPriorityStats(priorityStats);
|
||||||
|
|
||||||
|
return statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WorkOrderStatisticsVO getMyWorkOrderStatistics() {
|
||||||
|
Long userId = SecurityUtils.getCurrentUserId();
|
||||||
|
log.info("获取我的工单统计信息, userId: {}", userId);
|
||||||
|
|
||||||
|
WorkOrderStatisticsVO statistics = new WorkOrderStatisticsVO();
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
LambdaQueryWrapper<WorkOrder> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(WorkOrder::getDeleted, 0)
|
||||||
|
.eq(WorkOrder::getHandlerId, userId);
|
||||||
|
|
||||||
|
List<WorkOrder> orders = workOrderMapper.selectList(wrapper);
|
||||||
|
|
||||||
|
// 统计总数
|
||||||
|
statistics.setTotalCount(orders.size());
|
||||||
|
|
||||||
|
// 按状态统计
|
||||||
|
Map<String, Long> statusCount = orders.stream()
|
||||||
|
.collect(Collectors.groupingBy(WorkOrder::getStatus, Collectors.counting()));
|
||||||
|
statistics.setPendingCount(statusCount.getOrDefault("pending", 0L).intValue());
|
||||||
|
statistics.setAssignedCount(statusCount.getOrDefault("assigned", 0L).intValue());
|
||||||
|
statistics.setProcessingCount(statusCount.getOrDefault("processing", 0L).intValue());
|
||||||
|
statistics.setCompletedCount(statusCount.getOrDefault("resolved", 0L).intValue());
|
||||||
|
statistics.setClosedCount(statusCount.getOrDefault("closed", 0L).intValue());
|
||||||
|
statistics.setRejectedCount(0);
|
||||||
|
|
||||||
|
// 统计超期未完成
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
int overdueCount = (int) orders.stream()
|
||||||
|
.filter(o -> !"resolved".equals(o.getStatus()) && !"closed".equals(o.getStatus()))
|
||||||
|
.filter(o -> o.getDeadline() != null && o.getDeadline().isBefore(now))
|
||||||
|
.count();
|
||||||
|
statistics.setOverdueCount(overdueCount);
|
||||||
|
|
||||||
|
// 统计即将超期(7天内)
|
||||||
|
LocalDateTime sevenDaysLater = now.plusDays(7);
|
||||||
|
int aboutToExpireCount = (int) orders.stream()
|
||||||
|
.filter(o -> !"resolved".equals(o.getStatus()) && !"closed".equals(o.getStatus()))
|
||||||
|
.filter(o -> o.getDeadline() != null
|
||||||
|
&& !o.getDeadline().isBefore(now)
|
||||||
|
&& !o.getDeadline().isAfter(sevenDaysLater))
|
||||||
|
.count();
|
||||||
|
statistics.setAboutToExpireCount(aboutToExpireCount);
|
||||||
|
|
||||||
|
// 按类型统计
|
||||||
|
Map<String, Long> typeStats = orders.stream()
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
o -> o.getOrderType() != null ? o.getOrderType() : "other",
|
||||||
|
Collectors.counting()
|
||||||
|
));
|
||||||
|
statistics.setTypeStats(typeStats);
|
||||||
|
|
||||||
|
// 按优先级统计
|
||||||
|
Map<String, Long> priorityStats = orders.stream()
|
||||||
|
.collect(Collectors.groupingBy(
|
||||||
|
o -> o.getPriority() != null ? o.getPriority() : "medium",
|
||||||
|
Collectors.counting()
|
||||||
|
));
|
||||||
|
statistics.setPriorityStats(priorityStats);
|
||||||
|
|
||||||
|
return statistics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成工单编号
|
||||||
|
*/
|
||||||
|
private String generateOrderCode() {
|
||||||
|
return "WO" + IdUtil.fastSimpleUUID().substring(0, 12).toUpperCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为VO
|
||||||
|
*/
|
||||||
|
private WorkOrderVO convertToWorkOrderVO(WorkOrder workOrder) {
|
||||||
|
WorkOrderVO vo = new WorkOrderVO();
|
||||||
|
vo.setId(workOrder.getId());
|
||||||
|
vo.setOrderCode(workOrder.getOrderCode());
|
||||||
|
vo.setOrderType(workOrder.getOrderType());
|
||||||
|
vo.setProjectId(workOrder.getProjectId());
|
||||||
|
vo.setRiskId(workOrder.getRiskId());
|
||||||
|
vo.setTitle(workOrder.getTitle());
|
||||||
|
vo.setDescription(workOrder.getDescription());
|
||||||
|
vo.setCreatorId(workOrder.getCreatorId());
|
||||||
|
vo.setHandlerId(workOrder.getHandlerId());
|
||||||
|
vo.setHandlerGroupId(workOrder.getHandlerGroupId());
|
||||||
|
vo.setPriority(workOrder.getPriority());
|
||||||
|
vo.setStatus(workOrder.getStatus());
|
||||||
|
vo.setSource(workOrder.getSource());
|
||||||
|
vo.setDeadline(workOrder.getDeadline());
|
||||||
|
vo.setAssignedTime(workOrder.getAssignedTime());
|
||||||
|
vo.setFirstResponseTime(workOrder.getFirstResponseTime());
|
||||||
|
vo.setResolvedTime(workOrder.getResolvedTime());
|
||||||
|
vo.setClosedTime(workOrder.getClosedTime());
|
||||||
|
vo.setSatisfactionScore(workOrder.getSatisfactionScore());
|
||||||
|
vo.setTags(workOrder.getTags());
|
||||||
|
vo.setCreateTime(workOrder.getCreateTime());
|
||||||
|
vo.setUpdateTime(workOrder.getUpdateTime());
|
||||||
|
|
||||||
|
// 判断是否超期
|
||||||
|
if (workOrder.getDeadline() != null
|
||||||
|
&& !"resolved".equals(workOrder.getStatus())
|
||||||
|
&& !"closed".equals(workOrder.getStatus())) {
|
||||||
|
vo.setIsOverdue(workOrder.getDeadline().isBefore(LocalDateTime.now()));
|
||||||
|
} else {
|
||||||
|
vo.setIsOverdue(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询项目名称
|
||||||
|
if (workOrder.getProjectId() != null) {
|
||||||
|
Project project = projectMapper.selectById(workOrder.getProjectId());
|
||||||
|
if (project != null) {
|
||||||
|
vo.setProjectName(project.getProjectName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询风险名称
|
||||||
|
if (workOrder.getRiskId() != null) {
|
||||||
|
Risk risk = riskMapper.selectById(workOrder.getRiskId());
|
||||||
|
if (risk != null) {
|
||||||
|
vo.setRiskName(risk.getRiskName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询创建人信息
|
||||||
|
if (workOrder.getCreatorId() != null) {
|
||||||
|
SysUser creator = sysUserMapper.selectById(workOrder.getCreatorId());
|
||||||
|
if (creator != null) {
|
||||||
|
vo.setCreatorName(creator.getRealName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询处理人信息
|
||||||
|
if (workOrder.getHandlerId() != null) {
|
||||||
|
SysUser handler = sysUserMapper.selectById(workOrder.getHandlerId());
|
||||||
|
if (handler != null) {
|
||||||
|
vo.setHandlerName(handler.getRealName());
|
||||||
|
vo.setHandlerAvatar(handler.getAvatar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return vo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
<result column="risk_level" property="riskLevel"/>
|
<result column="risk_level" property="riskLevel"/>
|
||||||
<result column="status" property="status"/>
|
<result column="status" property="status"/>
|
||||||
<result column="owner_id" property="ownerId"/>
|
<result column="owner_id" property="ownerId"/>
|
||||||
|
<result column="work_order_ids" property="workOrderIds" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
|
||||||
<result column="mitigation_plan" property="mitigationPlan"/>
|
<result column="mitigation_plan" property="mitigationPlan"/>
|
||||||
<result column="contingency_plan" property="contingencyPlan"/>
|
<result column="contingency_plan" property="contingencyPlan"/>
|
||||||
<result column="trigger_condition" property="triggerCondition"/>
|
<result column="trigger_condition" property="triggerCondition"/>
|
||||||
|
|||||||
163
src/main/resources/mapper/WorkOrderMapper.xml
Normal file
163
src/main/resources/mapper/WorkOrderMapper.xml
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
<?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.yinlihupo.mapper.WorkOrderMapper">
|
||||||
|
|
||||||
|
<!-- 通用 ResultMap -->
|
||||||
|
<resultMap id="BaseResultMap" type="WorkOrder">
|
||||||
|
<id column="id" property="id"/>
|
||||||
|
<result column="order_code" property="orderCode"/>
|
||||||
|
<result column="order_type" property="orderType"/>
|
||||||
|
<result column="project_id" property="projectId"/>
|
||||||
|
<result column="risk_id" property="riskId"/>
|
||||||
|
<result column="title" property="title"/>
|
||||||
|
<result column="description" property="description"/>
|
||||||
|
<result column="creator_id" property="creatorId"/>
|
||||||
|
<result column="handler_id" property="handlerId"/>
|
||||||
|
<result column="handler_group_id" property="handlerGroupId"/>
|
||||||
|
<result column="priority" property="priority"/>
|
||||||
|
<result column="status" property="status"/>
|
||||||
|
<result column="source" property="source"/>
|
||||||
|
<result column="deadline" property="deadline"/>
|
||||||
|
<result column="assigned_time" property="assignedTime"/>
|
||||||
|
<result column="first_response_time" property="firstResponseTime"/>
|
||||||
|
<result column="resolved_time" property="resolvedTime"/>
|
||||||
|
<result column="closed_time" property="closedTime"/>
|
||||||
|
<result column="satisfaction_score" property="satisfactionScore"/>
|
||||||
|
<result column="tags" property="tags" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
|
||||||
|
<result column="attachments" property="attachments" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
|
||||||
|
<result column="extra_data" property="extraData" typeHandler="cn.yinlihupo.common.handler.JsonbTypeHandler"/>
|
||||||
|
<result column="create_time" property="createTime"/>
|
||||||
|
<result column="update_time" property="updateTime"/>
|
||||||
|
<result column="deleted" property="deleted"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
|
<!-- 分页查询工单列表(含处理人信息,支持多条件筛选) -->
|
||||||
|
<select id="selectWorkOrderPageWithHandler" resultType="map">
|
||||||
|
SELECT wo.*,
|
||||||
|
u.real_name AS handler_name,
|
||||||
|
u.avatar AS handler_avatar,
|
||||||
|
c.real_name AS creator_name,
|
||||||
|
p.project_name,
|
||||||
|
r.risk_name
|
||||||
|
FROM work_order wo
|
||||||
|
LEFT JOIN sys_user u ON wo.handler_id = u.id AND u.deleted = 0
|
||||||
|
LEFT JOIN sys_user c ON wo.creator_id = c.id AND c.deleted = 0
|
||||||
|
LEFT JOIN project p ON wo.project_id = p.id AND p.deleted = 0
|
||||||
|
LEFT JOIN risk r ON wo.risk_id = r.id AND r.deleted = 0
|
||||||
|
WHERE wo.deleted = 0
|
||||||
|
<if test="projectId != null">
|
||||||
|
AND wo.project_id = #{projectId}
|
||||||
|
</if>
|
||||||
|
<if test="handlerId != null">
|
||||||
|
AND wo.handler_id = #{handlerId}
|
||||||
|
</if>
|
||||||
|
<if test="orderType != null and orderType != ''">
|
||||||
|
AND wo.order_type = #{orderType}
|
||||||
|
</if>
|
||||||
|
<if test="status != null and status != ''">
|
||||||
|
AND wo.status = #{status}
|
||||||
|
</if>
|
||||||
|
<if test="priority != null and priority != ''">
|
||||||
|
AND wo.priority = #{priority}
|
||||||
|
</if>
|
||||||
|
<if test="keyword != null and keyword != ''">
|
||||||
|
AND (wo.title LIKE CONCAT('%', #{keyword}, '%')
|
||||||
|
OR wo.description LIKE CONCAT('%', #{keyword}, '%'))
|
||||||
|
</if>
|
||||||
|
ORDER BY
|
||||||
|
CASE wo.priority
|
||||||
|
WHEN 'critical' THEN 1
|
||||||
|
WHEN 'high' THEN 2
|
||||||
|
WHEN 'medium' THEN 3
|
||||||
|
ELSE 4
|
||||||
|
END,
|
||||||
|
wo.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询我的工单列表 -->
|
||||||
|
<select id="selectMyWorkOrders" resultType="map">
|
||||||
|
SELECT wo.*,
|
||||||
|
p.project_name,
|
||||||
|
r.risk_name
|
||||||
|
FROM work_order wo
|
||||||
|
LEFT JOIN project p ON wo.project_id = p.id AND p.deleted = 0
|
||||||
|
LEFT JOIN risk r ON wo.risk_id = r.id AND r.deleted = 0
|
||||||
|
WHERE wo.handler_id = #{handlerId}
|
||||||
|
AND wo.deleted = 0
|
||||||
|
<if test="status != null and status != ''">
|
||||||
|
AND wo.status = #{status}
|
||||||
|
</if>
|
||||||
|
<if test="orderType != null and orderType != ''">
|
||||||
|
AND wo.order_type = #{orderType}
|
||||||
|
</if>
|
||||||
|
ORDER BY
|
||||||
|
CASE wo.priority
|
||||||
|
WHEN 'critical' THEN 1
|
||||||
|
WHEN 'high' THEN 2
|
||||||
|
WHEN 'medium' THEN 3
|
||||||
|
ELSE 4
|
||||||
|
END,
|
||||||
|
wo.create_time DESC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 按状态统计工单数量 -->
|
||||||
|
<select id="countByStatus" resultType="map">
|
||||||
|
SELECT status, COUNT(*) AS cnt
|
||||||
|
FROM work_order
|
||||||
|
WHERE deleted = 0
|
||||||
|
<if test="projectId != null">
|
||||||
|
AND project_id = #{projectId}
|
||||||
|
</if>
|
||||||
|
<if test="handlerId != null">
|
||||||
|
AND handler_id = #{handlerId}
|
||||||
|
</if>
|
||||||
|
GROUP BY status
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 按类型统计工单数量 -->
|
||||||
|
<select id="countByType" resultType="map">
|
||||||
|
SELECT order_type, COUNT(*) AS cnt
|
||||||
|
FROM work_order
|
||||||
|
WHERE project_id = #{projectId}
|
||||||
|
AND deleted = 0
|
||||||
|
GROUP BY order_type
|
||||||
|
ORDER BY order_type
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询即将超期的工单(N天内) -->
|
||||||
|
<select id="selectAboutToExpire" resultMap="BaseResultMap">
|
||||||
|
SELECT *
|
||||||
|
FROM work_order
|
||||||
|
WHERE deleted = 0
|
||||||
|
AND status NOT IN ('resolved', 'closed')
|
||||||
|
AND deadline IS NOT NULL
|
||||||
|
AND deadline >= CURRENT_TIMESTAMP
|
||||||
|
AND deadline <= CURRENT_TIMESTAMP + INTERVAL '1 day' * #{days}
|
||||||
|
<if test="projectId != null">
|
||||||
|
AND project_id = #{projectId}
|
||||||
|
</if>
|
||||||
|
<if test="handlerId != null">
|
||||||
|
AND handler_id = #{handlerId}
|
||||||
|
</if>
|
||||||
|
ORDER BY deadline ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- 查询超期未完成的工单 -->
|
||||||
|
<select id="selectOverdueWorkOrders" resultMap="BaseResultMap">
|
||||||
|
SELECT *
|
||||||
|
FROM work_order
|
||||||
|
WHERE deleted = 0
|
||||||
|
AND status NOT IN ('resolved', 'closed')
|
||||||
|
AND deadline IS NOT NULL
|
||||||
|
AND deadline < CURRENT_TIMESTAMP
|
||||||
|
<if test="projectId != null">
|
||||||
|
AND project_id = #{projectId}
|
||||||
|
</if>
|
||||||
|
<if test="handlerId != null">
|
||||||
|
AND handler_id = #{handlerId}
|
||||||
|
</if>
|
||||||
|
ORDER BY deadline ASC
|
||||||
|
</select>
|
||||||
|
|
||||||
|
</mapper>
|
||||||
Reference in New Issue
Block a user