Merge remote-tracking branch 'yudao/develop' into develop

# Conflicts:
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
This commit is contained in:
puhui999 2024-02-03 20:52:08 +08:00
commit 59dfa10461
42 changed files with 434 additions and 873 deletions

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.framework.flowable.core.enums;
/**
* 流程常量信息
*/
public interface BpmnModelConstants {
String BPMN_FILE_SUFFIX = ".bpmn";
/**
* BPMN 中的命名空间
*
* 这个东西有可能导致无法切换工作流程的实现
*/
String NAMESPACE = "http://flowable.org/bpmn";
/**
* 自定义属性 dataType
*/
String PROCESS_CUSTOM_DATA_TYPE = "dataType";
}

View File

@ -49,7 +49,7 @@ public class BpmProcessInstanceCopyController {
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "抄送流程") @Operation(summary = "抄送流程")
@PreAuthorize("@ss.hasPermission('bpm:process-instance-cc:create')") @PreAuthorize("@ss.hasPermission('bpm:process-instance-cc:create')")
public CommonResult<Boolean> createProcessInstanceCC(@Valid @RequestBody BpmProcessInstanceCopyCreateReqVO createReqVO) { public CommonResult<Boolean> createProcessInstanceCopy(@Valid @RequestBody BpmProcessInstanceCopyCreateReqVO createReqVO) {
processInstanceCopyService.createProcessInstanceCopy(getLoginUserId(), createReqVO); processInstanceCopyService.createProcessInstanceCopy(getLoginUserId(), createReqVO);
return success(true); return success(true);
} }
@ -57,7 +57,7 @@ public class BpmProcessInstanceCopyController {
@GetMapping("/my-page") @GetMapping("/my-page")
@Operation(summary = "获得抄送流程分页列表") @Operation(summary = "获得抄送流程分页列表")
@PreAuthorize("@ss.hasPermission('bpm:process-instance-cc:query')") @PreAuthorize("@ss.hasPermission('bpm:process-instance-cc:query')")
public CommonResult<PageResult<BpmProcessInstanceCopyPageItemRespVO>> getProcessInstanceCCPage( public CommonResult<PageResult<BpmProcessInstanceCopyPageItemRespVO>> getProcessInstanceCopyPage(
@Valid BpmProcessInstanceCopyMyPageReqVO pageReqVO) { @Valid BpmProcessInstanceCopyMyPageReqVO pageReqVO) {
PageResult<BpmProcessInstanceCopyDO> pageResult = processInstanceCopyService.getMyProcessInstanceCopyPage(getLoginUserId(), pageReqVO); PageResult<BpmProcessInstanceCopyDO> pageResult = processInstanceCopyService.getMyProcessInstanceCopyPage(getLoginUserId(), pageReqVO);
if (CollUtil.isEmpty(pageResult.getList())) { if (CollUtil.isEmpty(pageResult.getList())) {

View File

@ -1,65 +0,0 @@
package cn.iocoder.yudao.module.bpm.controller.admin.task.vo.task;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
// TODO @kyle1明确是 Req 还是 Resp2注释可以合并到 swagger 3example 写一下这样一些 mock 接口平台可以读取 example
/**
* 流程抄送视图对象
*/
@Data
public class BpmProcessInstanceCopyVO {
/**
* 编号
*/
@Schema(description = "抄送主键")
private Long id;
/**
* 发起人Id
*/
@Schema(description = "发起人Id")
private Long startUserId;
@Schema(description = "发起人别名")
private String startUserNickname;
/**
* 流程主键
*/
@Schema(description = "流程实例的主键")
private String processInstanceId;
@Schema(description = "流程实例的名字")
private String processInstanceName;
/**
* 任务主键
*/
@Schema(description = "发起抄送的任务编号")
private String taskId;
@Schema(description = "发起抄送的任务名称")
private String taskName;
/**
* 用户主键
*/
@Schema(description = "用户编号")
private Long userId;
@Schema(description = "用户别名")
private Long userNickname;
@Schema(description = "抄送原因")
private String reason;
@Schema(description = "抄送人")
private String creator;
@Schema(description = "抄送时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,76 @@
package cn.iocoder.yudao.module.bpm.framework.flowable.core.handler;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.delegate.DelegateExecution;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
// TODO @芋艿bpmn 分配人融合时需要搞下这块
/**
* 多实例处理类
*/
@AllArgsConstructor
@Component("multiInstanceHandler")
public class MultiInstanceHandler {
@Resource
private AdminUserApi userApi;
@Resource
private PermissionApi permissionApi;
/**
* 流程发起人那种情况不需要处理
* flowable 完成
*
* @param execution flowable的执行对象
* @return 用户ID
*/
public Set<String> getUserIds(DelegateExecution execution) {
Set<String> candidateUserIds = new LinkedHashSet<>();
FlowElement flowElement = execution.getCurrentFlowElement();
if (ObjectUtil.isNotEmpty(flowElement) && flowElement instanceof UserTask userTask) {
String dataType = userTask.getAttributeValue(BpmnModelConstants.NAMESPACE, BpmnModelConstants.PROCESS_CUSTOM_DATA_TYPE);
if ("USERS".equals(dataType) && CollUtil.isNotEmpty(userTask.getCandidateUsers())) {
// 添加候选用户id
candidateUserIds.addAll(userTask.getCandidateUsers());
} else if (CollUtil.isNotEmpty(userTask.getCandidateGroups())) {
// 获取组的ID角色ID集合或部门ID集合
List<Long> groups = userTask.getCandidateGroups().stream()
// 例如部门DEPT100100才是部门id
.map(item -> Long.parseLong(item.substring(4)))
.collect(Collectors.toList());
List<Long> userIds = new ArrayList<>();
if ("ROLES".equals(dataType)) {
// 通过角色id获取所有用户id集合
Set<Long> userRoleIdListByRoleIds = permissionApi.getUserRoleIdListByRoleIds(groups);
userIds = new ArrayList<>(userRoleIdListByRoleIds);
} else if ("DEPTS".equals(dataType)) {
// 通过部门id获取所有用户id集合
List<AdminUserRespDTO> userListByDeptIds = userApi.getUserListByDeptIds(groups);
userIds = convertList(userListByDeptIds, AdminUserRespDTO::getId);
}
// 添加候选用户id
userIds.forEach(id -> candidateUserIds.add(String.valueOf(id)));
}
}
return candidateUserIds;
}
}

View File

@ -5,8 +5,8 @@ import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.PageUtils; import cn.iocoder.yudao.framework.common.util.object.PageUtils;
import cn.iocoder.yudao.framework.flowable.core.enums.BpmnModelConstants;
import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils; import cn.iocoder.yudao.framework.flowable.core.util.BpmnModelUtils;
import cn.iocoder.yudao.framework.flowable.core.util.FlowableUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionListReqVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionPageItemRespVO;
@ -17,6 +17,8 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionExtMapper; import cn.iocoder.yudao.module.bpm.dal.mysql.definition.BpmProcessDefinitionExtMapper;
import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO; import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.converter.BpmnXMLConverter;
import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.BpmnModel;
@ -29,13 +31,10 @@ import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import java.util.*; import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_KEY_NOT_MATCH; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_KEY_NOT_MATCH;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_NAME_NOT_MATCH; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_DEFINITION_NAME_NOT_MATCH;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
@ -53,8 +52,6 @@ import static java.util.Collections.emptyList;
@Slf4j @Slf4j
public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionService { public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionService {
private static final String BPMN_FILE_SUFFIX = ".bpmn";
@Resource @Resource
private RepositoryService repositoryService; private RepositoryService repositoryService;
@ -125,7 +122,7 @@ public class BpmProcessDefinitionServiceImpl implements BpmProcessDefinitionServ
// 创建 Deployment 部署 // 创建 Deployment 部署
Deployment deploy = repositoryService.createDeployment() Deployment deploy = repositoryService.createDeployment()
.key(createReqDTO.getKey()).name(createReqDTO.getName()).category(createReqDTO.getCategory()) .key(createReqDTO.getKey()).name(createReqDTO.getName()).category(createReqDTO.getCategory())
.addBytes(createReqDTO.getKey() + BPMN_FILE_SUFFIX, createReqDTO.getBpmnBytes()) .addBytes(createReqDTO.getKey() + BpmnModelConstants.BPMN_FILE_SUFFIX, createReqDTO.getBpmnBytes())
.tenantId(TenantContextHolder.getTenantIdStr()) .tenantId(TenantContextHolder.getTenantIdStr())
.deploy(); .deploy();

View File

@ -10,8 +10,6 @@
<modules> <modules>
<module>yudao-module-crm-api</module> <module>yudao-module-crm-api</module>
<module>yudao-module-crm-biz</module> <module>yudao-module-crm-biz</module>
<module>yudao-module-bi-biz</module>
<module>yudao-module-bi-api</module>
</modules> </modules>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-crm</artifactId> <artifactId>yudao-module-crm</artifactId>

View File

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-crm</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-bi-api</artifactId>
<name>${project.artifactId}</name>
<description>
bi 模块 API暴露给其它模块调用
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-crm-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1 +0,0 @@
package cn.iocoder.yudao.module.bi.api;

View File

@ -1 +0,0 @@
package cn.iocoder.yudao.module.bi.enums;

View File

@ -1,74 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-crm</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-bi-biz</artifactId>
<name>${project.artifactId}</name>
<description>
crm 包下商业智能Business Intelligence
例如说:报表、图表、数据分析等等
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-crm-api</artifactId>
<version>${revision}</version>
</dependency>
<!-- 业务组件 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-operatelog</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-dict</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -1,9 +0,0 @@
### 合同金额排行榜
GET {{baseUrl}}/bi/rank/contract-ranKing
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 回款金额排行榜
GET {{baseUrl}}/bi/rank/receivables-ranKing
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@ -1,55 +0,0 @@
package cn.iocoder.yudao.module.bi.controller.admin.ranking;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiContractRanKingRespVO;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiRankReqVO;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiReceivablesRanKingRespVO;
import cn.iocoder.yudao.module.bi.service.ranking.BiRankingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
// TODO @anhaohao写了 swagger 注解不写注释哈
/**
* @author anhaohao
*/
@Tag(name = "管理后台 - 排行榜")
@RestController
@RequestMapping("/bi/ranking")
@Validated
public class BiRankingController {
@Resource
private BiRankingService biRankingService;
/**
* 合同金额排行榜
*/
@GetMapping("/contract-ranking")
@Operation(summary = "合同金额排行榜")
@PreAuthorize("@ss.hasPermission('bi:ranking:query')")
public CommonResult<List<BiContractRanKingRespVO>> contractAmountRanking(BiRankReqVO biRankReqVO) {
return success(biRankingService.contractRanKing(biRankReqVO));
}
/**
* 回款金额排行榜
*/
@GetMapping("/receivables-ranking")
@Operation(summary = "回款金额排行榜")
@PreAuthorize("@ss.hasPermission('bi:ranking:query')")
public CommonResult<List<BiReceivablesRanKingRespVO>> receivablesRanKing(BiRankReqVO biRankReqVO) {
return success(biRankingService.receivablesRanKing(biRankReqVO));
}
}

View File

@ -1,26 +0,0 @@
package cn.iocoder.yudao.module.bi.controller.admin.ranking.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
// TODO @anhaohaoVO 类有 swagger 注解不写注释哈
/**
* 管理后台 - BI 排行榜 Response VO
*
* @author anhaohao
*/
@Schema(description = "管理后台 - BI 合同金额排行榜 Response VO")
@Data
public class BiContractRanKingRespVO {
// TODO @anhaohao如果一定返回的字段需要加 requiredMode = Schema.RequiredMode.REQUIRED,
@Schema(description = "金额", example = "1")
private Integer price;
@Schema(description = "姓名", example = "1")
private String nickname;
@Schema(description = "部门名称", example = "1")
private String deptName;
}

View File

@ -1,44 +0,0 @@
package cn.iocoder.yudao.module.bi.controller.admin.ranking.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
// TODO @anhaohao这个类的命名还是保持和其它一致使用 ReqVO 结尾例如说CrmStatisticsCommonParamReqVO
/**
* @author anhaohao
* bi参数
*/
@EqualsAndHashCode(callSuper = true)
@Schema(description = "bi查询相关参数")
@Data
public class BiParams extends PageParam {
@Schema(description = "部门ID")
private Long deptId;
@Schema(description = "用户ID")
private Long userId;
@Schema(description = "用户IDs")
private List<Long> userIds;
// TODO @anhaohao这个字段可以融合到 startTimeendTime 里去交给前端计算哈
@Schema(description = "类型")
private String type;
// TODO @anhaohao还是使用 LocalDateTime
@Schema(description = "开始时间")
private String startTime;
@Schema(description = "结束时间")
private String endTime;
// TODO @anhaohao这个字段是不是直接只基于 deptId userId 来判断即可哈
@Schema(description = "0 部门 1员工")
private Integer isUser = 1;
}

View File

@ -1,22 +0,0 @@
package cn.iocoder.yudao.module.bi.controller.admin.ranking.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 管理后台 - 排行榜 Request VO
*
* @author anhaohao
*/
@Schema(description = "管理后台 - 排行榜 Request VO")
@Data
public class BiRankReqVO {
@Schema(description = "部门id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long deptId;
// TODO @anhaohao这个字段参考 BiParams type 建议
@Schema(description = "分析类型(1.今天 2.昨天 3.本周 4.上周 5.本月 6.上月 7.本季度 8.上季度 9.本年 10 上年)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String type;
}

View File

@ -1,25 +0,0 @@
package cn.iocoder.yudao.module.bi.controller.admin.ranking.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
// TODO @anhaohao参考 BiContractRanKingRespVO 的建议
/**
* 管理后台 - BI 排行榜 Response VO
*
* @author anhaohao
*/
@Schema(description = "管理后台 - BI 合同金额排行榜 Response VO")
@Data
public class BiReceivablesRanKingRespVO {
@Schema(description = "金额", example = "100")
private Integer price;
@Schema(description = "姓名", example = "张三")
private String nickname;
@Schema(description = "部门名称", example = "研发部")
private String deptName;
}

View File

@ -1,6 +0,0 @@
/**
* 提供 RESTful API 给前端
* 1. admin 提供给管理后台 yudao-ui-admin 前端项目
* 2. app 提供给用户 APP yudao-ui-app 前端项目它的 Controller VO 都要添加 App 前缀用于和管理后台进行区分
*/
package cn.iocoder.yudao.module.bi.controller;

View File

@ -1,31 +0,0 @@
package cn.iocoder.yudao.module.bi.dal.mysql;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiContractRanKingRespVO;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiReceivablesRanKingRespVO;
import cn.iocoder.yudao.module.bi.util.BiTimeUtil;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* @author anhaohao
*/
@Mapper
public interface BiRankingMapper extends BaseMapperX {
/**
* 合同金额排行榜
*
* @param biTimeEntity 参数
* @return List<BiContractAmountRankingRespVO>
*/
List<BiContractRanKingRespVO> contractRanKing(BiTimeUtil.BiTimeEntity biTimeEntity);
/**
* 回款金额排行榜
*
* @param biTimeEntity 参数
* @return List<BiContractAmountRankingRespVO>
*/
List<BiReceivablesRanKingRespVO> receivablesRanKing(BiTimeUtil.BiTimeEntity biTimeEntity);
}

View File

@ -1,6 +0,0 @@
/**
* 属于 bi 模块的 framework 封装
*
* @author 芋道源码
*/
package cn.iocoder.yudao.module.bi.framework;

View File

@ -1,24 +0,0 @@
package cn.iocoder.yudao.module.bi.framework.web.config;
import cn.iocoder.yudao.framework.swagger.config.YudaoSwaggerAutoConfiguration;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* bi 模块的 web 组件的 Configuration
*
* @author 芋道源码
*/
@Configuration(proxyBeanMethods = false)
public class BiWebConfiguration {
/**
* bi 模块的 API 分组
*/
@Bean
public GroupedOpenApi biGroupedOpenApi() {
return YudaoSwaggerAutoConfiguration.buildGroupedOpenApi("bi");
}
}

View File

@ -1,4 +0,0 @@
/**
* bi 模块的 web 配置
*/
package cn.iocoder.yudao.module.bi.framework.web;

View File

@ -1,10 +0,0 @@
/**
* crm 包下商业智能Business Intelligence
* 例如说报表图表数据分析等等
* <p>
* 1. Controller URL /bi/ 开头避免和其它 Module 冲突
*
* TODO @anhaohaomall 当时独立拆分一个 statistics 模块的原因是因为 mall 拆分了多个模块没有模块适合承接统计的能力所以独立了
* TODO crm 因为没有拆分所以可以直接放在 crm 模块下面这样我们可以在 controller/admin service 新建一个 bi 专门放置统计的代码
*/
package cn.iocoder.yudao.module.bi;

View File

@ -1,30 +0,0 @@
package cn.iocoder.yudao.module.bi.service.ranking;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiContractRanKingRespVO;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiRankReqVO;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiReceivablesRanKingRespVO;
import java.util.List;
/**
* BI 排行榜 Service 接口
*
* @author anhaohao
*/
public interface BiRankingService { // TODO @anhaohao第一个方法和类要有一个空行
/**
* 合同金额排行榜
*
* @param biRankReqVO 参数
* @return List<BiContractAmountRankingRespVO>
*/
List<BiContractRanKingRespVO> contractRanKing(BiRankReqVO biRankReqVO);
/**
* 回款金额排行榜
*
* @param biRankReqVO 参数
* @return List<BiContractAmountRankingRespVO>
*/
List<BiReceivablesRanKingRespVO> receivablesRanKing(BiRankReqVO biRankReqVO);
}

View File

@ -1,53 +0,0 @@
package cn.iocoder.yudao.module.bi.service.ranking;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiContractRanKingRespVO;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiParams;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiRankReqVO;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiReceivablesRanKingRespVO;
import cn.iocoder.yudao.module.bi.dal.mysql.BiRankingMapper;
import cn.iocoder.yudao.module.bi.util.BiTimeUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.List;
/**
* @author anhaohao
*/
@Service(value = "biRankService")
@Validated
public class BiRankingServiceImpl implements BiRankingService {
@Resource
private BiRankingMapper biRankingMapper;
@Override
public List<BiContractRanKingRespVO> contractRanKing(BiRankReqVO biRankReqVO) {
BiParams biParams = new BiParams();
biParams.setType(biRankReqVO.getType());
biParams.setDeptId(biRankReqVO.getDeptId());
biParams.setIsUser(0);
BiTimeUtil.BiTimeEntity biTimeEntity = BiTimeUtil.analyzeType(biParams);
List<Long> userIds = biTimeEntity.getUserIds();
if (userIds.isEmpty()) {
return new ArrayList<>();
}
return biRankingMapper.contractRanKing(biTimeEntity);
}
@Override
public List<BiReceivablesRanKingRespVO> receivablesRanKing(BiRankReqVO biRankReqVO) {
BiParams biParams = new BiParams();
biParams.setType(biRankReqVO.getType());
biParams.setDeptId(biRankReqVO.getDeptId());
biParams.setIsUser(0);
BiTimeUtil.BiTimeEntity biTimeEntity = BiTimeUtil.analyzeType(biParams);
List<Long> userIds = biTimeEntity.getUserIds();
if (userIds.isEmpty()) {
return new ArrayList<>();
}
return biRankingMapper.receivablesRanKing(biTimeEntity);
}
}

View File

@ -1,250 +0,0 @@
package cn.iocoder.yudao.module.bi.util;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiParams;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* bi时间工具类
*
* @author anhaohao
*/
public class BiTimeUtil {
public static BiTimeEntity analyzeType(BiParams biParams) {
// 解析时间
BiTimeEntity biTimeEntity = analyzeTime(biParams);
// 解析权限
// TODO @anhaohao涉及到数据的读取不放在 Util 还是搞会到 Service
biTimeEntity.setUserIds(analyzeAuth(biParams));
return biTimeEntity;
}
/**
* 解析权限
*
* @param biParams bi参数
* @return List<Long>
*/
public static List<Long> analyzeAuth(BiParams biParams) {
List<Long> userIdList = new ArrayList<>();
Long deptId = biParams.getDeptId();
Long userId = biParams.getUserId();
Integer isUser = biParams.getIsUser();
// 获取部门和用户的api
DeptApi deptApi = SpringUtil.getBean("deptApiImpl");
AdminUserApi adminUserApi = SpringUtil.getBean("adminUserApiImpl");
// 0.部门 1.用户
if (isUser == 0) {
if (deptId == null) {
deptId = adminUserApi.getUser(SecurityFrameworkUtils.getLoginUserId()).getDeptId();
}
List<DeptRespDTO> childDeptList = deptApi.getChildDeptList(deptId);
List<Long> deptIds = new ArrayList<>();
deptIds.add(deptId);
if (childDeptList != null && !childDeptList.isEmpty()) {
for (DeptRespDTO deptRespDTO : childDeptList) {
deptIds.add(deptRespDTO.getId());
}
}
// 获取部门下的用户
adminUserApi.getUserListByDeptIds(deptIds).forEach(adminUserRespDTO -> userIdList.add(adminUserRespDTO.getId()));
} else {
if (userId == null) {
List<AdminUserRespDTO> userListBySubordinate = adminUserApi.getUserListBySubordinate(SecurityFrameworkUtils.getLoginUserId());
userListBySubordinate.forEach(adminUserRespDTO -> userIdList.add(adminUserRespDTO.getId()));
} else {
userIdList.add(userId);
}
}
return userIdList;
}
/**
* 解析时间
*
* @param biParams bi参数
* @return BiTimeEntity
*/
public static BiTimeEntity analyzeTime(BiParams biParams) {
Date beginDate = DateUtil.date();
Date endDate = DateUtil.date();
int cycleNum = 12;
String sqlDateFormat = "%Y%m";
String dateFormat = "yyyyMM";
String type = biParams.getType();
String startTime = biParams.getStartTime();
String endTime = biParams.getEndTime();
if (StrUtil.isNotEmpty(type)) {
//1.今天 2.昨天 3.本周 4.上周 5.本月 6.上月 7.本季度 8.上季度 9.本年 10 上年
switch (type) {
case "1":
beginDate = DateUtil.beginOfDay(DateUtil.date());
endDate = DateUtil.endOfDay(DateUtil.date());
sqlDateFormat = "%Y%m%d";
dateFormat = "yyyyMMdd";
cycleNum = 1;
break;
case "2":
beginDate = DateUtil.beginOfDay(new Date(System.currentTimeMillis() - 86400000));
endDate = DateUtil.endOfDay(new Date(System.currentTimeMillis() - 86400000));
sqlDateFormat = "%Y%m%d";
dateFormat = "yyyyMMdd";
cycleNum = 1;
break;
case "3":
beginDate = DateUtil.beginOfWeek(DateUtil.date());
endDate = DateUtil.endOfWeek(DateUtil.date());
sqlDateFormat = "%Y%m%d";
dateFormat = "yyyyMMdd";
cycleNum = 7;
break;
case "4":
beginDate = DateUtil.beginOfWeek(DateUtil.offsetWeek(DateUtil.date(), -1));
endDate = DateUtil.endOfWeek(DateUtil.offsetWeek(DateUtil.date(), -1));
sqlDateFormat = "%Y%m%d";
dateFormat = "yyyyMMdd";
cycleNum = 7;
break;
case "5":
beginDate = DateUtil.beginOfMonth(DateUtil.date());
endDate = DateUtil.endOfMonth(DateUtil.date());
sqlDateFormat = "%Y%m%d";
dateFormat = "yyyyMMdd";
cycleNum = (int) DateUtil.between(beginDate, endDate, DateUnit.DAY) + 1;
break;
case "6":
beginDate = DateUtil.beginOfMonth(DateUtil.offsetMonth(DateUtil.date(), -1));
endDate = DateUtil.endOfMonth(DateUtil.offsetMonth(DateUtil.date(), -1));
sqlDateFormat = "%Y%m%d";
dateFormat = "yyyyMMdd";
cycleNum = (int) DateUtil.between(beginDate, endDate, DateUnit.DAY) + 1;
break;
case "7":
beginDate = DateUtil.beginOfQuarter(DateUtil.date());
endDate = DateUtil.endOfQuarter(DateUtil.date());
cycleNum = 3;
break;
case "8":
beginDate = DateUtil.beginOfQuarter(DateUtil.offsetMonth(DateUtil.date(), -3));
endDate = DateUtil.endOfQuarter(DateUtil.offsetMonth(DateUtil.date(), -3));
cycleNum = 3;
break;
case "9":
beginDate = DateUtil.beginOfYear(DateUtil.date());
endDate = DateUtil.endOfYear(DateUtil.date());
break;
case "10":
beginDate = DateUtil.beginOfYear(DateUtil.offsetMonth(DateUtil.date(), -12));
endDate = DateUtil.endOfYear(DateUtil.offsetMonth(DateUtil.date(), -12));
break;
default:
break;
}
} else if (StrUtil.isNotEmpty(startTime) && StrUtil.isNotEmpty(endTime)) {
Date start;
Date end;
if (startTime.length() == 6) {
start = DateUtil.parse(startTime, "yyyyMM");
end = DateUtil.endOfMonth(DateUtil.parse(endTime, "yyyyMM"));
} else {
start = DateUtil.parse(startTime);
end = DateUtil.parse(endTime);
}
Integer startMonth = Integer.valueOf(DateUtil.format(start, "yyyyMM"));
int endMonth = Integer.parseInt(DateUtil.format(end, "yyyyMM"));
if (startMonth.equals(endMonth)) {
sqlDateFormat = "%Y%m%d";
dateFormat = "yyyyMMdd";
long diffDay = DateUtil.between(start, end, DateUnit.DAY);
cycleNum = (int) diffDay + 1;
} else {
sqlDateFormat = "%Y%m";
dateFormat = "yyyyMM";
int diffYear = Integer.parseInt(Integer.toString(endMonth).substring(0, 4)) - Integer.parseInt(startMonth.toString().substring(0, 4));
int diffMonth = endMonth % 100 - startMonth % 100 + 1;
cycleNum = diffYear * 12 + diffMonth;
}
beginDate = start;
endDate = end;
}
Integer beginTime = Integer.valueOf(DateUtil.format(beginDate, dateFormat));
Integer finalTime = Integer.valueOf(DateUtil.format(endDate, dateFormat));
return new BiTimeEntity(sqlDateFormat, dateFormat, beginDate, endDate, cycleNum, beginTime, finalTime, new ArrayList<>());
}
@Data
@Accessors(chain = true)
public static class BiTimeEntity {
/**
* sql日期格式化
*/
private String sqlDateFormat;
/**
* 日期格式化
*/
private String dateFormat;
/**
* 开始时间
*/
private Date beginDate;
/**
* 结束时间
*/
private Date endDate;
/**
* 周期
*/
private Integer cycleNum;
/**
* 开始时间 字符串格式 如20200101
*/
private Integer beginTime;
/**
* 结束时间 字符串格式 如20200101
*/
private Integer finalTime;
/**
* user列表
*/
private List<Long> userIds = new ArrayList<>();
private Integer page;
private Integer limit;
public BiTimeEntity(String sqlDateFormat, String dateFormat, Date beginDate, Date endDate, Integer cycleNum, Integer beginTime, Integer finalTime, List<Long> userIds) {
this.sqlDateFormat = sqlDateFormat;
this.dateFormat = dateFormat;
this.beginDate = beginDate;
this.endDate = endDate;
this.cycleNum = cycleNum;
this.beginTime = beginTime;
this.finalTime = finalTime;
this.userIds = userIds;
}
public BiTimeEntity() {
}
}
}

View File

@ -1,45 +0,0 @@
<?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.bi.dal.mysql.BiRankingMapper">
<select id="contractRanKing"
resultType="cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiContractRanKingRespVO">
SELECT IFNULL(SUM(t.price), 0) AS price, su.nickname, t.owner_user_id, dept.name AS deptName
FROM crm_contract t
<!-- TODO @anhaohaosystem_users、system_dept 是不是没用到?尽量不连这 2 个表,微服务下会是独立仓库;如果显示需要,可以在 service 读取后拼接; -->
LEFT JOIN system_users AS su ON su.id = t.owner_user_id
LEFT JOIN system_dept AS dept ON dept.id = su.dept_id
WHERE t.deleted = 0
AND t.audit_status = 20
<if test="userIds != null and userIds.size() > 0">
and t.owner_user_id in
<foreach collection="userIds" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
<!-- TODO @anhaohao在某个区间的时间这么做有点浪费性能一般做法是 beginTime 是开始时间00:00:00finalTime 是结束时间23:59:59这样实现的 -->
AND DATE_FORMAT(t.order_date,'${sqlDateFormat}') between #{beginTime} and #{finalTime}
GROUP BY t.owner_user_id
ORDER BY price DESC
</select>
<select id="receivablesRanKing"
resultType="cn.iocoder.yudao.module.bi.controller.admin.ranking.vo.BiReceivablesRanKingRespVO">
SELECT IFNULL(SUM(t.price), 0) AS price, su.nickname, t.owner_user_id, dept.name AS deptName
FROM crm_receivable t
LEFT JOIN system_users AS su ON su.id = t.owner_user_id
LEFT JOIN system_dept AS dept ON dept.id = su.dept_id
WHERE t.deleted = 0
AND t.audit_status = 20
<if test="userIds != null and userIds.size() > 0">
and t.owner_user_id in
<foreach collection="userIds" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
AND DATE_FORMAT(t.return_time,'${sqlDateFormat}') between #{beginTime} and #{finalTime}
GROUP BY t.owner_user_id
ORDER BY price DESC
</select>
</mapper>

View File

@ -0,0 +1,9 @@
### 合同金额排行榜
GET {{baseUrl}}/crm/bi-rank/get-contract-price-rank?deptId=100&times[0]=2022-12-12 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 回款金额排行榜
GET {{baseUrl}}/crm/bi-rank/get-receivable-price-rank?deptId=100&times[0]=2022-12-12 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.crm.controller.admin.bi;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO;
import cn.iocoder.yudao.module.crm.service.bi.CrmBiRankingService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - CRM BI 排行榜")
@RestController
@RequestMapping("/crm/bi-rank")
@Validated
public class CrmBiRankController {
@Resource
private CrmBiRankingService rankingService;
@GetMapping("/get-contract-price-rank")
@Operation(summary = "获得合同金额排行榜")
@PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
public CommonResult<List<CrmBiRanKRespVO>> getContractPriceRank(@Valid CrmBiRankReqVO rankingReqVO) {
return success(rankingService.getContractPriceRank(rankingReqVO));
}
@GetMapping("/get-receivable-price-rank")
@Operation(summary = "获得回款金额排行榜")
@PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")
public CommonResult<List<CrmBiRanKRespVO>> getReceivablePriceRank(@Valid CrmBiRankReqVO rankingReqVO) {
return success(rankingService.getReceivablePriceRank(rankingReqVO));
}
}

View File

@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.crm.controller.admin.bi.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM BI 排行榜 Response VO")
@Data
public class CrmBiRanKRespVO {
@Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long ownerUserId;
@Schema(description = "姓名", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String nickname;
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String deptName;
/**
* 数量是个特别抽象的概念在不同排行下代表不同含义
*
* 1. 金额合同金额排行回款金额排行
* 2. 个数签约合同排行产品销量排行产品销量排行新增客户数排行新增联系人排行跟进次数排行跟进客户数排行
*/
@Schema(description = "数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer count;
}

View File

@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.crm.controller.admin.bi.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
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 = "管理后台 - CRM BI 排行榜 Request VO")
@Data
public class CrmBiRankReqVO {
@Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "部门 id 不能为空")
private Long deptId;
/**
* userIds 目前不用前端传递目前是方便后端通过 deptId 读取编号后设置回来
*
* 后续可能会支持选择部分用户进行查询
*/
@Schema(description = "负责人用户 id 集合", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2")
private List<Long> userIds;
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.REQUIRED)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@NotEmpty(message = "时间范围不能为空")
private LocalDateTime[] times;
}

View File

@ -28,6 +28,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -55,6 +56,7 @@ public class CrmReceivablePlanController {
@Resource @Resource
private CrmReceivableService receivableService; private CrmReceivableService receivableService;
@Resource @Resource
@Lazy
private CrmContractService contractService; private CrmContractService contractService;
@Resource @Resource
private CrmCustomerService customerService; private CrmCustomerService customerService;

View File

@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.crm.dal.mysql.bi;
import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* CRM BI 排行榜 Mapper
*
* @author anhaohao
*/
@Mapper
public interface CrmBiRankingMapper {
/**
* 查询合同金额排行榜
*
* @param rankReqVO 参数
* @return 合同金额排行榜
*/
List<CrmBiRanKRespVO> selectContractPriceRank(CrmBiRankReqVO rankReqVO);
/**
* 查询回款金额排行榜
*
* @param rankReqVO 参数
* @return 回款金额排行榜
*/
List<CrmBiRanKRespVO> selectReceivablePriceRank(CrmBiRankReqVO rankReqVO);
}

View File

@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.crm.service.bi;
import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO;
import java.util.List;
/**
* CRM BI 排行榜 Service 接口
*
* @author anhaohao
*/
public interface CrmBiRankingService {
/**
* 获得合同金额排行榜
*
* @param rankReqVO 排行参数
* @return 合同金额排行榜
*/
List<CrmBiRanKRespVO> getContractPriceRank(CrmBiRankReqVO rankReqVO);
/**
* 获得回款金额排行榜
*
* @param rankReqVO 排行参数
* @return 回款金额排行榜
*/
List<CrmBiRanKRespVO> getReceivablePriceRank(CrmBiRankReqVO rankReqVO);
}

View File

@ -0,0 +1,104 @@
package cn.iocoder.yudao.module.crm.service.bi;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO;
import cn.iocoder.yudao.module.crm.dal.mysql.bi.CrmBiRankingMapper;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
/**
* CRM BI 排行榜 Service 实现类
*
* @author anhaohao
*/
@Service
@Validated
public class CrmBiRankingServiceImpl implements CrmBiRankingService {
@Resource
private CrmBiRankingMapper biRankingMapper;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@Override
public List<CrmBiRanKRespVO> getContractPriceRank(CrmBiRankReqVO rankReqVO) {
return getRank(rankReqVO, biRankingMapper::selectContractPriceRank);
}
@Override
public List<CrmBiRanKRespVO> getReceivablePriceRank(CrmBiRankReqVO rankReqVO) {
return getRank(rankReqVO, biRankingMapper::selectReceivablePriceRank);
}
/**
* 获得排行版数据
*
* @param rankReqVO 参数
* @param rankFunction 排行榜方法
* @return 排行版数据
*/
private List<CrmBiRanKRespVO> getRank(CrmBiRankReqVO rankReqVO, Function<CrmBiRankReqVO, List<CrmBiRanKRespVO>> rankFunction) {
// 1. 获得用户编号数组
rankReqVO.setUserIds(getUserIds(rankReqVO.getDeptId()));
if (CollUtil.isEmpty(rankReqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 获得排行数据
List<CrmBiRanKRespVO> ranks = rankFunction.apply(rankReqVO);
if (CollUtil.isEmpty(ranks)) {
return Collections.emptyList();
}
ranks.sort(Comparator.comparing(CrmBiRanKRespVO::getCount).reversed());
// 3. 拼接用户信息
appendUserInfo(ranks);
return ranks;
}
/**
* 拼接用户信息昵称部门
*
* @param ranks 排行榜数据
*/
private void appendUserInfo(List<CrmBiRanKRespVO> ranks) {
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(ranks, CrmBiRanKRespVO::getOwnerUserId));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
ranks.forEach(rank -> MapUtils.findAndThen(userMap, rank.getOwnerUserId(), user -> {
rank.setNickname(user.getNickname());
MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> rank.setDeptName(dept.getName()));
}));
}
/**
* 获得部门下的用户编号数组包括子部门的
*
* @param deptId 部门编号
* @return 用户编号数组
*/
public List<Long> getUserIds(Long deptId) {
// 1. 获得部门列表
List<Long> deptIds = convertList(deptApi.getChildDeptList(deptId), DeptRespDTO::getId);
deptIds.add(deptId);
// 2. 获得用户编号
return convertList(adminUserApi.getUserListByDeptIds(deptIds), AdminUserRespDTO::getId);
}
}

View File

@ -57,7 +57,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
@Resource @Resource
private CrmBusinessProductService businessProductService; private CrmBusinessProductService businessProductService;
@Resource @Resource
@Lazy @Lazy // 延迟加载避免循环依赖
private CrmContractService contractService; private CrmContractService contractService;
@Resource @Resource
private CrmPermissionService permissionService; private CrmPermissionService permissionService;

View File

@ -24,6 +24,7 @@ import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord; import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;

View File

@ -27,6 +27,7 @@ import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.service.impl.DiffParseFunction;
import com.mzt.logapi.starter.annotation.LogRecord; import com.mzt.logapi.starter.annotation.LogRecord;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;

View File

@ -0,0 +1,33 @@
<?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.crm.dal.mysql.bi.CrmBiRankingMapper">
<select id="selectContractPriceRank"
resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
SELECT IFNULL(SUM(price), 0) AS count, owner_user_id
FROM crm_contract
WHERE deleted = 0
AND audit_status = 20
and owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND order_date between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
</select>
<select id="selectReceivablePriceRank"
resultType="cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO">
SELECT IFNULL(SUM(price), 0) AS count, owner_user_id
FROM crm_receivable
WHERE deleted = 0
AND audit_status = 20
and owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND return_time between #{times[0],javaType=java.time.LocalDateTime} and #{times[1],javaType=java.time.LocalDateTime}
GROUP BY owner_user_id
</select>
</mapper>

View File

@ -20,7 +20,6 @@ import java.util.List;
public class DeptApiImpl implements DeptApi { public class DeptApiImpl implements DeptApi {
@Resource @Resource
@Lazy // 延迟加载解决相互依赖的问题
private DeptService deptService; private DeptService deptService;
@Override @Override

View File

@ -44,6 +44,9 @@ public class AuthPermissionInfoRespVO {
@Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.jpg") @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.jpg")
private String avatar; private String avatar;
@Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long deptId;
} }
@Schema(description = "管理后台 - 登录用户的菜单信息 Response VO") @Schema(description = "管理后台 - 登录用户的菜单信息 Response VO")

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.convert.auth; package cn.iocoder.yudao.module.system.convert.auth;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeSendReqDTO;
import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO; import cn.iocoder.yudao.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
@ -29,7 +30,7 @@ public interface AuthConvert {
default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) { default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
return AuthPermissionInfoRespVO.builder() return AuthPermissionInfoRespVO.builder()
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build()) .user(BeanUtils.toBean(user, AuthPermissionInfoRespVO.UserVO.class))
.roles(convertSet(roleList, RoleDO::getCode)) .roles(convertSet(roleList, RoleDO::getCode))
// 权限标识信息 // 权限标识信息
.permissions(convertSet(menuList, MenuDO::getPermission)) .permissions(convertSet(menuList, MenuDO::getPermission))

View File

@ -93,11 +93,6 @@
<artifactId>yudao-module-crm-biz</artifactId> <artifactId>yudao-module-crm-biz</artifactId>
<version>${revision}</version> <version>${revision}</version>
</dependency> </dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-bi-biz</artifactId>
<version>${revision}</version>
</dependency>
<!-- ERP 相关模块。默认注释,保证编译速度 --> <!-- ERP 相关模块。默认注释,保证编译速度 -->
<dependency> <dependency>