# Conflicts:
#	pom.xml
#	yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/portrait/CrmStatisticsPortraitReqVO.java
#	yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsPerformanceServiceImpl.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dept/vo/post/PostSaveReqVO.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dict/vo/data/DictDataPageReqVO.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/dict/vo/data/DictDataSaveReqVO.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/menu/MenuRespVO.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/menu/MenuSaveVO.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleRespVO.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/permission/vo/role/RoleSaveReqVO.java
#	yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/permission/MenuServiceImpl.java
pull/92/MERGE
YunaiV 2024-05-01 09:32:36 +08:00
commit 9d8f924fec
56 changed files with 1120 additions and 2016 deletions

View File

@ -14,6 +14,11 @@
如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。 如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
## 🐰 版本说明
* JDK 8 + Spring Boot 2.7 版本:<https://gitee.com/zhijiantianya/yudao-cloud>`master` 分支
* JDK 17/21 + Spring Boot 3.2 版本:<https://gitee.com/zhijiantianya/yudao-cloud>`master-jdk17` 分支
## 🐶 新手必读 ## 🐶 新手必读
* 演示地址【Vue3 + element-plus】<http://dashboard-vue3.yudao.iocoder.cn> * 演示地址【Vue3 + element-plus】<http://dashboard-vue3.yudao.iocoder.cn>
@ -32,7 +37,7 @@
![架构图](/.image/common/yudao-cloud-architecture.png) ![架构图](/.image/common/yudao-cloud-architecture.png)
* Java 后端:`master` 分支为 JDK 8 + Spring Boot 2.7.18`master-jdk21` 分支为 JDK21 + Spring Boot 3.2.0 * Java 后端:`master` 分支为 JDK 8 + Spring Boot 2.7`master-jdk17` 分支为 JDK 17/21 + Spring Boot 3.2
* 管理后台的电脑端Vue3 提供 [element-plus](https://gitee.com/yudaocode/yudao-ui-admin-vue3)、[vben(ant-design-vue)](https://gitee.com/yudaocode/yudao-ui-admin-vben) 两个版本Vue2 提供 [element-ui](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin) 版本 * 管理后台的电脑端Vue3 提供 [element-plus](https://gitee.com/yudaocode/yudao-ui-admin-vue3)、[vben(ant-design-vue)](https://gitee.com/yudaocode/yudao-ui-admin-vben) 两个版本Vue2 提供 [element-ui](https://gitee.com/zhijiantianya/ruoyi-vue-pro/tree/master/yudao-ui-admin) 版本
* 管理后台的移动端:采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5 * 管理后台的移动端:采用 [uni-app](https://github.com/dcloudio/uni-app) 方案,一份代码多终端适配,同时支持 APP、小程序、H5
* 后端采用 Spring Cloud Alibaba 微服务架构,注册中心 + 配置中心 Nacos定时任务 XXL-Job服务保障 Sentinel服务网关 Gateway分布式事务 Seata * 后端采用 Spring Cloud Alibaba 微服务架构,注册中心 + 配置中心 Nacos定时任务 XXL-Job服务保障 Sentinel服务网关 Gateway分布式事务 Seata
@ -72,11 +77,6 @@
| [yudao-ui-admin-uniapp](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-uniapp/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-uniapp.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-uniapp) | 基于 Vue2 + element-ui 实现的管理后台 | | [yudao-ui-admin-uniapp](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-admin-uniapp/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-admin-uniapp) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-admin-uniapp.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-admin-uniapp) | 基于 Vue2 + element-ui 实现的管理后台 |
| [yudao-ui-go-view](https://gitee.com/yudaocode/yudao-ui-go-view) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-go-view/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-go-view) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-go-view.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-go-view) | 基于 Vue3 + naive-ui 实现的大屏报表 | | [yudao-ui-go-view](https://gitee.com/yudaocode/yudao-ui-go-view) | [![Gitee star](https://gitee.com/yudaocode/yudao-ui-go-view/badge/star.svg?theme=white)](https://gitee.com/yudaocode/yudao-ui-go-view) [![GitHub stars](https://img.shields.io/github/stars/yudaocode/yudao-ui-go-view.svg?style=social&label=Stars)](https://github.com/yudaocode/yudao-ui-go-view) | 基于 Vue3 + naive-ui 实现的大屏报表 |
## 🐰 分支说明
* JDK 8 + Spring Boot 2.7.18 版本:<https://gitee.com/zhijiantianya/yudao-cloud>`master` 分支
* JDK 21 + Spring Boot 3.2.0 版本:<https://gitee.com/zhijiantianya/yudao-cloud>`master-jdk21` 分支
## 😎 开源协议 ## 😎 开源协议
**为什么推荐使用本项目?** **为什么推荐使用本项目?**
@ -103,16 +103,9 @@
![功能分层](/.image/common/ruoyi-vue-pro-biz.png) ![功能分层](/.image/common/ruoyi-vue-pro-biz.png)
* 系统功能 * 通用模块(必选):系统功能、基础设施
* 基础设施 * 通用模块(可选):工作流程、支付系统、数据报表、会员中心
* 工作流程 * 业务系统按需ERP 系统、CRM 系统、商城系统、微信公众号
* 支付系统
* 会员中心
* 数据报表
* 商城系统
* 微信公众号
* ERP 系统
* CRM 系统
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。 > 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
> >

File diff suppressed because it is too large Load Diff

View File

@ -97,6 +97,10 @@ public class CollectionUtils {
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
public static <T> Set<T> convertSet(Collection<T> from) {
return convertSet(from, v -> v);
}
public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) { public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {
if (CollUtil.isEmpty(from)) { if (CollUtil.isEmpty(from)) {
return new HashSet<>(); return new HashSet<>();

View File

@ -1,12 +1,11 @@
package cn.iocoder.yudao.framework.mq.rabbitmq.config; package cn.iocoder.yudao.framework.mq.rabbitmq.config;
import cn.hutool.core.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.utils.SerializationUtils; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import java.lang.reflect.Field;
/** /**
* RabbitMQ * RabbitMQ
@ -18,12 +17,12 @@ import java.lang.reflect.Field;
@ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate") @ConditionalOnClass(name = "org.springframework.amqp.rabbit.core.RabbitTemplate")
public class YudaoRabbitMQAutoConfiguration { public class YudaoRabbitMQAutoConfiguration {
static { /**
// 强制设置 SerializationUtils 的 TRUST_ALL 为 true避免 RabbitMQ Consumer 反序列化消息报错 * Jackson2JsonMessageConverter Bean使 jackson
// 为什么不通过设置 spring.amqp.deserialization.trust.all 呢?因为可能在 SerializationUtils static 初始化后 */
Field trustAllField = ReflectUtil.getField(SerializationUtils.class, "TRUST_ALL"); @Bean
ReflectUtil.removeFinalModify(trustAllField); public MessageConverter createMessageConverter() {
ReflectUtil.setFieldValue(SerializationUtils.class, trustAllField, true); return new Jackson2JsonMessageConverter();
} }
} }

View File

@ -43,7 +43,7 @@ public class BaseDbAndRedisUnitTest {
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类 YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
RedisAutoConfiguration.class, // Spring Redis 自动配置类 RedisAutoConfiguration.class, // Spring Redis 自动配置类
RedissonAutoConfiguration.class, // Redisson 自动配置类 RedissonAutoConfiguration.class, // Redisson 自动配置类
}) })
public static class Application { public static class Application {
} }

View File

@ -24,7 +24,7 @@ public class BaseRedisUnitTest {
RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer RedisTestConfiguration.class, // Redis 测试配置类,用于启动 RedisServer
RedisAutoConfiguration.class, // Spring Redis 自动配置类 RedisAutoConfiguration.class, // Spring Redis 自动配置类
YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类 YudaoRedisAutoConfiguration.class, // 自己的 Redis 配置类
RedissonAutoConfiguration.class, // Redisson 自动配置类 RedissonAutoConfiguration.class, // Redisson 自动配置类
}) })
public static class Application { public static class Application {
} }

View File

@ -177,7 +177,7 @@ public class CrmBusinessController {
buildBusinessDetailList(list)); buildBusinessDetailList(list));
} }
private List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) { public List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
if (CollUtil.isEmpty(list)) { if (CollUtil.isEmpty(list)) {
return Collections.emptyList(); return Collections.emptyList();
} }

View File

@ -53,3 +53,13 @@ tenant-id: {{adminTenentId}}
GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59 GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-user?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}} Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}} tenant-id: {{adminTenentId}}
### 6.3 获取客户成交周期(按区域)
GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-area?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}
### 6.4 获取客户成交周期(按产品)
GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-product?deptId=100&times[0]=2023-01-01 00:00:00&times[1]=2024-12-12 23:59:59
Authorization: Bearer {{token}}
tenant-id: {{adminTenentId}}

View File

@ -96,6 +96,18 @@ public class CrmStatisticsCustomerController {
return success(customerService.getCustomerDealCycleByUser(reqVO)); return success(customerService.getCustomerDealCycleByUser(reqVO));
} }
// TODO dhb52【成交周期分析】里有按照员工已实现、地区未实现、产品未实现需要在看看哈可以把 CustomerDealCycle 拆成 3 个 tab员工客户成交周期分析、地区客户成交周期分析、产品客户成交周期分析 @GetMapping("/get-customer-deal-cycle-by-area")
@Operation(summary = "获取客户成交周期(按用户)")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsCustomerDealCycleByAreaRespVO>> getCustomerDealCycleByArea(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getCustomerDealCycleByArea(reqVO));
}
@GetMapping("/get-customer-deal-cycle-by-product")
@Operation(summary = "获取客户成交周期(按用户)")
@PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')")
public CommonResult<List<CrmStatisticsCustomerDealCycleByProductRespVO>> getCustomerDealCycleByProduct(@Valid CrmStatisticsCustomerReqVO reqVO) {
return success(customerService.getCustomerDealCycleByProduct(reqVO));
}
} }

View File

@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.business.CrmBusinessController;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsFunnelService;
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 销售漏斗")
@RestController
@RequestMapping("/crm/statistics-funnel")
@Validated
public class CrmStatisticsFunnelController {
@Resource
private CrmStatisticsFunnelService funnelService;
@GetMapping("/get-funnel-summary")
@Operation(summary = "获取销售漏斗统计数据", description = "用于【销售漏斗】页面的【销售漏斗分析】")
@PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
public CommonResult<CrmStatisticFunnelSummaryRespVO> getFunnelSummary(@Valid CrmStatisticsFunnelReqVO reqVO) {
return success(funnelService.getFunnelSummary(reqVO));
}
@GetMapping("/get-business-summary-by-end-status")
@Operation(summary = "获取商机结束状态统计", description = "用于【销售漏斗】页面的【销售漏斗分析】")
@PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
public CommonResult<List<CrmStatisticsBusinessSummaryByEndStatusRespVO>> getBusinessSummaryByEndStatus(@Valid CrmStatisticsFunnelReqVO reqVO) {
return success(funnelService.getBusinessSummaryByEndStatus(reqVO));
}
@GetMapping("/get-business-summary-by-date")
@Operation(summary = "获取新增商机分析(按日期)", description = "用于【销售漏斗】页面")
@PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
public CommonResult<List<CrmStatisticsBusinessSummaryByDateRespVO>> getBusinessSummaryByDate(@Valid CrmStatisticsFunnelReqVO reqVO) {
return success(funnelService.getBusinessSummaryByDate(reqVO));
}
@GetMapping("/get-business-inversion-rate-summary-by-date")
@Operation(summary = "获取商机转化率分析(按日期)", description = "用于【销售漏斗】页面")
@PreAuthorize("@ss.hasPermission('crm:statistics-funnel:query')")
public CommonResult<List<CrmStatisticsBusinessInversionRateSummaryByDateRespVO>> getBusinessInversionRateSummaryByDate(@Valid CrmStatisticsFunnelReqVO reqVO) {
return success(funnelService.getBusinessInversionRateSummaryByDate(reqVO));
}
@GetMapping("/get-business-page-by-date")
@Operation(summary = "获得商机分页(按日期)", description = "用于【销售漏斗】页面的【新增商机分析】")
@PreAuthorize("@ss.hasPermission('crm:business:query')")
public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPageByDate(@Valid CrmStatisticsFunnelReqVO pageVO) {
PageResult<CrmBusinessDO> pageResult = funnelService.getBusinessPageByDate(pageVO);
return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
}
private List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
return SpringUtil.getBean(CrmBusinessController.class).buildBusinessDetailList(list);
}
}

View File

@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 客户成交周期分析(按区域) VO")
@Data
public class CrmStatisticsCustomerDealCycleByAreaRespVO {
@Schema(description = "省份编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@JsonIgnore
private Integer areaId;
@Schema(description = "省份名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "浙江省")
private String areaName;
@Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
private Double customerDealCycle;
@Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerDealCount;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 客户成交周期分析(按产品) VO")
@Data
public class CrmStatisticsCustomerDealCycleByProductRespVO {
@Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "演示产品")
private String productName;
@Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0")
private Double customerDealCycle;
@Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer customerDealCount;
}

View File

@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - CRM 销售漏斗 Response VO")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class CrmStatisticFunnelSummaryRespVO {
@Schema(description = "客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long customerCount;
@Schema(description = "商机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long businessCount;
@Schema(description = "赢单数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long businessWinCount;
}

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - CRM 商机转化率分析(按日期) VO")
@Data
public class CrmStatisticsBusinessInversionRateSummaryByDateRespVO {
@Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
private String time;
@Schema(description = "商机数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long businessCount;
@Schema(description = "赢单商机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long businessWinCount;
}

View File

@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 新增商机分析(按日期) VO")
@Data
public class CrmStatisticsBusinessSummaryByDateRespVO {
@Schema(description = "时间轴", requiredMode = Schema.RequiredMode.REQUIRED, example = "202401")
private String time;
@Schema(description = "新增商机数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long businessCreateCount;
@Schema(description = "新增商机金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private BigDecimal totalPrice;
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Schema(description = "管理后台 - CRM 商机结束状态统计 Response VO")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class CrmStatisticsBusinessSummaryByEndStatusRespVO {
@Schema(description = "结束状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer endStatus;
@Schema(description = "商机数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long businessCount;
@Schema(description = "商机总金额,单位:元", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private BigDecimal totalPrice;
}

View File

@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel;
import cn.iocoder.yudao.framework.common.enums.DateIntervalEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
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 销售漏斗 Request VO")
@Data
public class CrmStatisticsFunnelReqVO extends PageParam {
@Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "部门 id 不能为空")
private Long deptId;
/**
* id, ,
*/
@Schema(description = "负责人用户 id", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
private Long userId;
/**
* userIds 便 deptId
*
*/
@Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
private List<Long> userIds;
@Schema(description = "时间间隔类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@InEnum(value = DateIntervalEnum.class, message = "时间间隔类型,必须是 {value}")
private Integer interval;
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Size(min = 2, max = 2, message = "请选择时间范围")
private LocalDateTime[] times;
}

View File

@ -1,10 +1,11 @@
package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait; package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.portrait;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat; import org.springframework.format.annotation.DateTimeFormat;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.List; import java.util.List;
@ -31,12 +32,9 @@ public class CrmStatisticsPortraitReqVO {
@Schema(description = "负责人用户 id 集合", hidden = true, example = "2") @Schema(description = "负责人用户 id 集合", hidden = true, example = "2")
private List<Long> userIds; private List<Long> userIds;
/**
* , -, , -
* Mapper
*/
@Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED) @Schema(description = "时间范围", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Size(min = 2, max = 2, message = "请选择时间范围")
private LocalDateTime[] times; private LocalDateTime[] times;
} }

View File

@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils;
@ -58,10 +59,16 @@ public interface CrmBusinessMapper extends BaseMapperX<CrmBusinessDO> {
return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId); return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId);
} }
default List<CrmBusinessDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId){ default List<CrmBusinessDO> selectListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId) {
return selectList(new LambdaQueryWrapperX<CrmBusinessDO>() return selectList(new LambdaQueryWrapperX<CrmBusinessDO>()
.eq(CrmBusinessDO::getCustomerId, customerId) .eq(CrmBusinessDO::getCustomerId, customerId)
.eq(CrmBusinessDO::getOwnerUserId, ownerUserId)); .eq(CrmBusinessDO::getOwnerUserId, ownerUserId));
} }
default PageResult<CrmBusinessDO> selectPage(CrmStatisticsFunnelReqVO pageVO) {
return selectPage(pageVO, new LambdaQueryWrapperX<CrmBusinessDO>()
.in(CrmBusinessDO::getOwnerUserId, pageVO.getUserIds())
.betweenIfPresent(CrmBusinessDO::getCreateTime, pageVO.getTimes()));
}
} }

View File

@ -53,6 +53,7 @@ public interface CrmStatisticsCustomerMapper {
/** /**
* () * ()
*
* @return @return @param reqVO * @return @return @param reqVO
* @return * @return
*/ */
@ -191,4 +192,20 @@ public interface CrmStatisticsCustomerMapper {
*/ */
List<CrmStatisticsCustomerDealCycleByUserRespVO> selectCustomerDealCycleGroupByUser(CrmStatisticsCustomerReqVO reqVO); List<CrmStatisticsCustomerDealCycleByUserRespVO> selectCustomerDealCycleGroupByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerDealCycleByAreaRespVO> selectCustomerDealCycleGroupByAreaId(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerDealCycleByProductRespVO> selectCustomerDealCycleGroupByProductId(CrmStatisticsCustomerReqVO reqVO);
} }

View File

@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.crm.dal.mysql.statistics;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessInversionRateSummaryByDateRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByEndStatusRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* CRM Mapper
*
* @author HUIHUI
*/
@Mapper
public interface CrmStatisticsFunnelMapper {
Long selectCustomerCountByDate(CrmStatisticsFunnelReqVO reqVO);
Long selectBusinessCountByDateAndEndStatus(@Param("reqVO") CrmStatisticsFunnelReqVO reqVO, @Param("status") Integer status);
List<CrmStatisticsBusinessSummaryByEndStatusRespVO> selectBusinessSummaryListGroupByEndStatus(CrmStatisticsFunnelReqVO reqVO);
List<CrmStatisticsBusinessSummaryByDateRespVO> selectBusinessSummaryGroupByDate(CrmStatisticsFunnelReqVO reqVO);
List<CrmStatisticsBusinessInversionRateSummaryByDateRespVO> selectBusinessInversionRateSummaryByDate(CrmStatisticsFunnelReqVO reqVO);
}

View File

@ -5,6 +5,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateStatusReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateStatusReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
@ -194,4 +195,12 @@ public interface CrmBusinessService {
*/ */
List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId); List<CrmBusinessDO> getBusinessListByCustomerIdOwnerUserId(Long customerId, Long ownerUserId);
/**
*
*
* @param pageVO
* @return
*/
PageResult<CrmBusinessDO> getBusinessPageByDate(CrmStatisticsFunnelReqVO pageVO);
} }

View File

@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusi
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateStatusReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateStatusReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsFunnelReqVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
@ -375,4 +376,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService {
return businessMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId); return businessMapper.selectListByCustomerIdOwnerUserId(customerId, ownerUserId);
} }
@Override
public PageResult<CrmBusinessDO> getBusinessPageByDate(CrmStatisticsFunnelReqVO pageVO) {
return businessMapper.selectPage(pageVO);
}
} }

View File

@ -120,7 +120,7 @@ public class CrmClueServiceImpl implements CrmClueService {
} }
@Override @Override
@LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}", @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}}",
success = CRM_CLUE_FOLLOW_UP_SUCCESS) success = CRM_CLUE_FOLLOW_UP_SUCCESS)
@CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.WRITE)
public void updateClueFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) { public void updateClueFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) {

View File

@ -93,4 +93,20 @@ public interface CrmStatisticsCustomerService {
*/ */
List<CrmStatisticsCustomerDealCycleByUserRespVO> getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO); List<CrmStatisticsCustomerDealCycleByUserRespVO> getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerDealCycleByAreaRespVO> getCustomerDealCycleByArea(CrmStatisticsCustomerReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsCustomerDealCycleByProductRespVO> getCustomerDealCycleByProduct(CrmStatisticsCustomerReqVO reqVO);
} }

View File

@ -4,6 +4,9 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.ip.core.Area;
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*; import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*;
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper; import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper;
import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@ -20,6 +23,7 @@ import java.time.LocalDateTime;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
@ -291,6 +295,46 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe
return summaryList; return summaryList;
} }
@Override
public List<CrmStatisticsCustomerDealCycleByAreaRespVO> getCustomerDealCycleByArea(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
List<Long> userIds = getUserIds(reqVO);
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
reqVO.setUserIds(userIds);
// 2. 获取客户地区统计数据
List<CrmStatisticsCustomerDealCycleByAreaRespVO> dealCycleByAreaList = customerMapper.selectCustomerDealCycleGroupByAreaId(reqVO);
if (CollUtil.isEmpty(dealCycleByAreaList)) {
return Collections.emptyList();
}
// 3. 拼接数据
Map<Integer, Area> areaMap = convertMap(AreaUtils.getByType(AreaTypeEnum.PROVINCE, Function.identity()), Area::getId);
return convertList(dealCycleByAreaList, vo -> {
if (vo.getAreaId() != null) {
Integer parentId = AreaUtils.getParentIdByType(vo.getAreaId(), AreaTypeEnum.PROVINCE);
findAndThen(areaMap, parentId, area -> vo.setAreaId(parentId).setAreaName(area.getName()));
}
return vo;
});
}
@Override
public List<CrmStatisticsCustomerDealCycleByProductRespVO> getCustomerDealCycleByProduct(CrmStatisticsCustomerReqVO reqVO) {
// 1. 获得用户编号数组
List<Long> userIds = getUserIds(reqVO);
if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList();
}
reqVO.setUserIds(userIds);
// 2. 获取客户产品统计数据
// TODO @dhb52未读取产品名
return customerMapper.selectCustomerDealCycleGroupByProductId(reqVO);
}
/** /**
* *
* *

View File

@ -0,0 +1,56 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import java.util.List;
/**
* CRM Service
*
* @author HUIHUI
*/
public interface CrmStatisticsFunnelService {
/**
*
*
* @param reqVO
* @return
*/
CrmStatisticFunnelSummaryRespVO getFunnelSummary(CrmStatisticsFunnelReqVO reqVO);
/**
*
*
* @param reqVO
* @return
*/
List<CrmStatisticsBusinessSummaryByEndStatusRespVO> getBusinessSummaryByEndStatus(CrmStatisticsFunnelReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsBusinessSummaryByDateRespVO> getBusinessSummaryByDate(CrmStatisticsFunnelReqVO reqVO);
/**
* ()
*
* @param reqVO
* @return
*/
List<CrmStatisticsBusinessInversionRateSummaryByDateRespVO> getBusinessInversionRateSummaryByDate(CrmStatisticsFunnelReqVO reqVO);
/**
* ()
*
* @param pageVO
* @return
*/
PageResult<CrmBusinessDO> getBusinessPageByDate(CrmStatisticsFunnelReqVO pageVO);
}

View File

@ -0,0 +1,153 @@
package cn.iocoder.yudao.module.crm.service.statistics;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.*;
import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsFunnelMapper;
import cn.iocoder.yudao.module.crm.enums.business.CrmBusinessEndStatusEnum;
import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
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 java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* CRM Service
*
* @author HUIHUI
*/
@Service
public class CrmStatisticsFunnelServiceImpl implements CrmStatisticsFunnelService {
@Resource
private CrmStatisticsFunnelMapper funnelMapper;
@Resource
private AdminUserApi adminUserApi;
@Resource
private CrmBusinessService businessService;
@Resource
private DeptApi deptApi;
@Override
public CrmStatisticFunnelSummaryRespVO getFunnelSummary(CrmStatisticsFunnelReqVO reqVO) {
// 1. 获得用户编号数组
List<Long> userIds = getUserIds(reqVO);
if (CollUtil.isEmpty(userIds)) {
return null;
}
reqVO.setUserIds(userIds);
// 2. 获得漏斗数据
Long customerCount = funnelMapper.selectCustomerCountByDate(reqVO);
Long businessCount = funnelMapper.selectBusinessCountByDateAndEndStatus(reqVO, null);
Long businessWinCount = funnelMapper.selectBusinessCountByDateAndEndStatus(reqVO, CrmBusinessEndStatusEnum.WIN.getStatus());
return new CrmStatisticFunnelSummaryRespVO(customerCount, businessCount, businessWinCount);
}
@Override
public List<CrmStatisticsBusinessSummaryByEndStatusRespVO> getBusinessSummaryByEndStatus(CrmStatisticsFunnelReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 获得统计数据
return funnelMapper.selectBusinessSummaryListGroupByEndStatus(reqVO);
}
@Override
public List<CrmStatisticsBusinessSummaryByDateRespVO> getBusinessSummaryByDate(CrmStatisticsFunnelReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 按天统计,获取分项统计数据
List<CrmStatisticsBusinessSummaryByDateRespVO> businessSummaryList = funnelMapper.selectBusinessSummaryGroupByDate(reqVO);
// 3. 按照日期间隔,合并数据
List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
return convertList(timeRanges, times -> {
Long businessCreateCount = businessSummaryList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
.mapToLong(CrmStatisticsBusinessSummaryByDateRespVO::getBusinessCreateCount).sum();
BigDecimal businessDealCount = businessSummaryList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
.map(CrmStatisticsBusinessSummaryByDateRespVO::getTotalPrice)
.reduce(BigDecimal.ZERO, BigDecimal::add);
return new CrmStatisticsBusinessSummaryByDateRespVO()
.setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
.setBusinessCreateCount(businessCreateCount).setTotalPrice(businessDealCount);
});
}
@Override
public List<CrmStatisticsBusinessInversionRateSummaryByDateRespVO> getBusinessInversionRateSummaryByDate(CrmStatisticsFunnelReqVO reqVO) {
// 1. 获得用户编号数组
reqVO.setUserIds(getUserIds(reqVO));
if (CollUtil.isEmpty(reqVO.getUserIds())) {
return Collections.emptyList();
}
// 2. 按天统计,获取分项统计数据
List<CrmStatisticsBusinessInversionRateSummaryByDateRespVO> businessSummaryList = funnelMapper.selectBusinessInversionRateSummaryByDate(reqVO);
// 3. 按照日期间隔,合并数据
List<LocalDateTime[]> timeRanges = LocalDateTimeUtils.getDateRangeList(reqVO.getTimes()[0], reqVO.getTimes()[1], reqVO.getInterval());
return convertList(timeRanges, times -> {
Long businessCount = businessSummaryList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
.mapToLong(CrmStatisticsBusinessInversionRateSummaryByDateRespVO::getBusinessCount).sum();
Long businessWinCount = businessSummaryList.stream()
.filter(vo -> LocalDateTimeUtils.isBetween(times[0], times[1], vo.getTime()))
.mapToLong(CrmStatisticsBusinessInversionRateSummaryByDateRespVO::getBusinessWinCount).sum();
return new CrmStatisticsBusinessInversionRateSummaryByDateRespVO()
.setTime(LocalDateTimeUtils.formatDateRange(times[0], times[1], reqVO.getInterval()))
.setBusinessCount(businessCount).setBusinessWinCount(businessWinCount);
});
}
@Override
public PageResult<CrmBusinessDO> getBusinessPageByDate(CrmStatisticsFunnelReqVO pageVO) {
// 1. 获得用户编号数组
pageVO.setUserIds(getUserIds(pageVO));
if (CollUtil.isEmpty(pageVO.getUserIds())) {
return PageResult.empty();
}
// 2. 执行查询
return businessService.getBusinessPageByDate(pageVO);
}
/**
* ,
*
* @param reqVO
* @return
*/
private List<Long> getUserIds(CrmStatisticsFunnelReqVO reqVO) {
// 情况一:选中某个用户
if (ObjUtil.isNotNull(reqVO.getUserId())) {
return List.of(reqVO.getUserId());
}
// 情况二:选中某个部门
// 2.1 获得部门列表
List<Long> deptIds = convertList(deptApi.getChildDeptList(reqVO.getDeptId()).getCheckedData(), DeptRespDTO::getId);
deptIds.add(reqVO.getDeptId());
// 2.2 获得用户编号
return convertList(adminUserApi.getUserListByDeptIds(deptIds).getCheckedData(), AdminUserRespDTO::getId);
}
}

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.crm.service.statistics; package cn.iocoder.yudao.module.crm.service.statistics;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO; import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceReqVO;
import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO; import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO;
@ -9,11 +10,12 @@ 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.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.google.common.collect.Lists; import jakarta.annotation.Resource;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource; import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
@ -39,7 +41,7 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform
@Override @Override
public List<CrmStatisticsPerformanceRespVO> getContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO) { public List<CrmStatisticsPerformanceRespVO> getContractCountPerformance(CrmStatisticsPerformanceReqVO performanceReqVO) {
// TODO @scholar我们可以换个思路实现,减少数据库的计算量 // TODO @scholar可以把下面这个注释,你理解后,重新整理下,写到 getPerformance 里
// 比如说2024 年的合同数据,是不是 2022-12 到 2024-12-31每个月的统计呢 // 比如说2024 年的合同数据,是不是 2022-12 到 2024-12-31每个月的统计呢
// 理解之后,我们可以数据 group by 年-月20222-12 到 2024-12-31 的,然后内存在聚合出 CrmStatisticsPerformanceRespVO 这样 // 理解之后,我们可以数据 group by 年-月20222-12 到 2024-12-31 的,然后内存在聚合出 CrmStatisticsPerformanceRespVO 这样
// 这样,我们就可以减少数据库的计算量,提升性能;同时 SQL 也会很简单,开发者理解起来也简单哈; // 这样,我们就可以减少数据库的计算量,提升性能;同时 SQL 也会很简单,开发者理解起来也简单哈;
@ -56,28 +58,99 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform
return getPerformance(performanceReqVO, performanceMapper::selectReceivablePricePerformance); return getPerformance(performanceReqVO, performanceMapper::selectReceivablePricePerformance);
} }
// TODO @scholar代码注释应该有 3 个变量哈;
/** /**
* *
* *
* @param performanceReqVO * @param performanceReqVO
* @param performanceFunction * @param performanceFunction
* @return * @return
*/ */
// TODO @scholar下面一行的变量超过一行了阅读不美观可以考虑每一行一个变量
private List<CrmStatisticsPerformanceRespVO> getPerformance(CrmStatisticsPerformanceReqVO performanceReqVO, Function<CrmStatisticsPerformanceReqVO, private List<CrmStatisticsPerformanceRespVO> getPerformance(CrmStatisticsPerformanceReqVO performanceReqVO, Function<CrmStatisticsPerformanceReqVO,
List<CrmStatisticsPerformanceRespVO>> performanceFunction) { List<CrmStatisticsPerformanceRespVO>> performanceFunction) {
// TODO @scholar没使用到的变量建议删除
List<CrmStatisticsPerformanceRespVO> performanceRespVOList;
// 1. 获得用户编号数组 // 1. 获得用户编号数组
final List<Long> userIds = getUserIds(performanceReqVO); final List<Long> userIds = getUserIds(performanceReqVO);
if (CollUtil.isEmpty(userIds)) { if (CollUtil.isEmpty(userIds)) {
return Collections.emptyList(); return Collections.emptyList();
} }
performanceReqVO.setUserIds(userIds); performanceReqVO.setUserIds(userIds);
// 2. 获得排行数据 // TODO @scholar1. 和 2. 之间,可以考虑换一行;保证每一块逻辑的间隔;
// 2. 获得业绩数据
// TODO @scholar复数变量建议使用 s 或者 list 结果;这里用 performanceList 好列;
List<CrmStatisticsPerformanceRespVO> performance = performanceFunction.apply(performanceReqVO); List<CrmStatisticsPerformanceRespVO> performance = performanceFunction.apply(performanceReqVO);
if (CollUtil.isEmpty(performance)) {
return Collections.emptyList(); // 获取查询的年份
// TODO @scholar逻辑可以简化一下
// TODO 1把 performance 转换成 mapkey 是 timevalue 是 count
// TODO 2当前年遍历 1-12 月份,去 map 拿到 count接着月份 -1去 map 拿 count再年份 -1拿 count
String currentYear = LocalDateTimeUtil.format(performanceReqVO.getTimes()[0],"yyyy");
// 构造查询当年和前一年每年12个月的年月组合
List<String> allMonths = new ArrayList<>();
for (int year = Integer.parseInt(currentYear)-1; year <= Integer.parseInt(currentYear); year++) {
for (int month = 1; month <= 12; month++) {
allMonths.add(String.format("%d%02d", year, month));
} }
return performance; }
List<CrmStatisticsPerformanceRespVO> computedList = new ArrayList<>();
List<CrmStatisticsPerformanceRespVO> respVOList = new ArrayList<>();
// 生成computedList基础数据
// 构造完整的2*12个月的数据如果某月数据缺失需要补上0一年12个月不能有缺失
for (String month : allMonths) {
CrmStatisticsPerformanceRespVO foundData = performance.stream()
.filter(data -> data.getTime().equals(month))
.findFirst()
.orElse(null);
if (foundData != null) {
computedList.add(foundData);
} else {
CrmStatisticsPerformanceRespVO missingData = new CrmStatisticsPerformanceRespVO();
missingData.setTime(month);
missingData.setCurrentMonthCount(BigDecimal.ZERO);
missingData.setLastMonthCount(BigDecimal.ZERO);
missingData.setLastYearCount(BigDecimal.ZERO);
computedList.add(missingData);
}
}
//根据查询年份和前一年的数据,计算查询年份的同比环比数据
for (CrmStatisticsPerformanceRespVO currentData : computedList) {
String currentMonth = currentData.getTime();
// 根据当年和前一年的月销售数据计算currentYear的完整数据
if (currentMonth.startsWith(currentYear)) {
// 计算 LastMonthCount
int currentIndex = computedList.indexOf(currentData);
if (currentIndex > 0) {
CrmStatisticsPerformanceRespVO lastMonthData = computedList.get(currentIndex - 1);
currentData.setLastMonthCount(lastMonthData.getCurrentMonthCount());
} else {
currentData.setLastMonthCount(BigDecimal.ZERO); // 第一个月的 LastMonthCount 设为0
}
// 计算 LastYearCount
String lastYearMonth = String.valueOf(Integer.parseInt(currentMonth) - 100);
CrmStatisticsPerformanceRespVO lastYearData = computedList.stream()
.filter(data -> data.getTime().equals(lastYearMonth))
.findFirst()
.orElse(null);
if (lastYearData != null) {
currentData.setLastYearCount(lastYearData.getCurrentMonthCount());
} else {
currentData.setLastYearCount(BigDecimal.ZERO); // 如果去年同月数据不存在设为0
}
respVOList.add(currentData);//给前端只需要返回查询当年的数据,不需要前一年数据
}
}
return respVOList;
} }
/** /**
@ -89,7 +162,7 @@ public class CrmStatisticsPerformanceServiceImpl implements CrmStatisticsPerform
private List<Long> getUserIds(CrmStatisticsPerformanceReqVO reqVO) { private List<Long> getUserIds(CrmStatisticsPerformanceReqVO reqVO) {
// 情况一:选中某个用户 // 情况一:选中某个用户
if (ObjUtil.isNotNull(reqVO.getUserId())) { if (ObjUtil.isNotNull(reqVO.getUserId())) {
return Lists.newArrayList(reqVO.getUserId()); return List.of(reqVO.getUserId());
} }
// 情况二:选中某个部门 // 情况二:选中某个部门
// 2.1 获得部门列表 // 2.1 获得部门列表

View File

@ -21,7 +21,6 @@ import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
/** /**
* CRM Service * CRM Service
@ -56,15 +55,18 @@ public class CrmStatisticsPortraitServiceImpl implements CrmStatisticsPortraitSe
// 3. 拼接数据 // 3. 拼接数据
List<Area> areaList = AreaUtils.getByType(AreaTypeEnum.PROVINCE, area -> area); List<Area> areaList = AreaUtils.getByType(AreaTypeEnum.PROVINCE, area -> area);
areaList.add(new Area().setId(null).setName("未知")); // TODO @puhui999是不是 65 find 的逻辑改下;不用 findAndThen直接从 areaMap 拿;拿到就设置,不拿到就设置 null 和 未知这样58 本行可以删除掉完事了;这样代码更简单和一致
Map<Integer, Area> areaMap = convertMap(areaList, Area::getId); Map<Integer, Area> areaMap = convertMap(areaList, Area::getId);
return convertList(list, item -> { return convertList(list, item -> {
Integer parentId = AreaUtils.getParentIdByType(item.getAreaId(), AreaTypeEnum.PROVINCE); Integer parentId = AreaUtils.getParentIdByType(item.getAreaId(), AreaTypeEnum.PROVINCE);
if (parentId == null) { // 找不到,归到未知 if (parentId != null) {
return item.setAreaId(null).setAreaName("未知"); Area area = areaMap.get(parentId);
} if (area != null) {
findAndThen(areaMap, parentId, area -> item.setAreaId(parentId).setAreaName(area.getName())); item.setAreaId(parentId).setAreaName(area.getName());
return item; return item;
}
}
// 找不到,归到未知
return item.setAreaId(null).setAreaName("未知");
}); });
} }

View File

@ -64,8 +64,10 @@ public class CrmPermissionUtils {
} }
// 2.2 场景二:我参与的数据 // 2.2 场景二:我参与的数据
if (CrmSceneTypeEnum.isInvolved(sceneType)) { if (CrmSceneTypeEnum.isInvolved(sceneType)) {
query.ne(ownerUserIdField, userId) query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType)
.in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel()); .eq(CrmPermissionDO::getBizId, bizId)
.in(CrmPermissionDO::getLevel, CrmPermissionLevelEnum.READ.getLevel(), CrmPermissionLevelEnum.WRITE.getLevel()));
query.ne(ownerUserIdField, userId);
} }
// 2.3 场景三:下属负责的数据 // 2.3 场景三:下属负责的数据
if (CrmSceneTypeEnum.isSubordinate(sceneType)) { if (CrmSceneTypeEnum.isSubordinate(sceneType)) {

View File

@ -16,20 +16,20 @@
GROUP BY time GROUP BY time
</select> </select>
<!-- TODO 芋艿:应该不用过滤时间 -->
<select id="selectCustomerDealCountGroupByDate" <select id="selectCustomerDealCountGroupByDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO"> resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerSummaryByDateRespVO">
SELECT DATE_FORMAT(customer.create_time, '%Y-%m-%d') AS time, SELECT DATE_FORMAT(customer.create_time, '%Y-%m-%d') AS time,
COUNT(DISTINCT customer.id) AS customer_deal_count COUNT(DISTINCT customer.id) AS customer_deal_count
FROM crm_customer AS customer FROM crm_customer AS customer
LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
WHERE customer.deleted = 0 AND contract.deleted = 0 WHERE customer.deleted = 0
AND contract.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND customer.owner_user_id IN AND customer.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=","> <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId} #{userId}
</foreach> </foreach>
AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY time GROUP BY time
</select> </select>
@ -53,13 +53,14 @@
COUNT(DISTINCT customer.id) AS customer_deal_count COUNT(DISTINCT customer.id) AS customer_deal_count
FROM crm_customer AS customer FROM crm_customer AS customer
LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id
WHERE customer.deleted = 0 AND contract.deleted = 0 WHERE customer.deleted = 0
AND contract.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND customer.owner_user_id IN AND customer.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=","> <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId} #{userId}
</foreach> </foreach>
AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY customer.owner_user_id GROUP BY customer.owner_user_id
</select> </select>
@ -221,4 +222,45 @@
GROUP BY customer.owner_user_id GROUP BY customer.owner_user_id
</select> </select>
<select id="selectCustomerDealCycleGroupByAreaId"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByAreaRespVO">
SELECT customer.area_id AS area_id,
IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle,
COUNT(DISTINCT customer.id) AS customer_deal_count
FROM crm_customer AS customer
LEFT JOIN crm_contract AS contract ON customer.id = contract.customer_id
WHERE customer.deleted = 0
AND contract.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND customer.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY
customer.area_id
</select>
<select id="selectCustomerDealCycleGroupByProductId"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.CrmStatisticsCustomerDealCycleByProductRespVO">
SELECT product.name AS product_name,
IFNULL(TRUNCATE(AVG(TIMESTAMPDIFF(DAY, customer.create_time, contract.order_date)), 1), 0) AS customer_deal_cycle,
COUNT(DISTINCT customer.id) AS customer_deal_count
FROM crm_customer AS customer
LEFT JOIN crm_contract AS contract ON customer.id = contract.customer_id
LEFT JOIN crm_contract_product AS contract_product ON contract_product.contract_id = contract.id
LEFT JOIN crm_product AS product ON contract_product.product_id = product.id
WHERE customer.deleted = 0
AND contract.deleted = 0
AND contract_product.deleted = 0
AND product.deleted = 0
AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status}
AND customer.owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime}
GROUP BY product.id
</select>
</mapper> </mapper>

View File

@ -0,0 +1,87 @@
<?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.statistics.CrmStatisticsFunnelMapper">
<select id="selectCustomerCountByDate" resultType="java.lang.Long">
SELECT
COUNT(*)
FROM crm_customer
WHERE deleted = 0
AND owner_user_id IN
<!-- TODO @puhui999这个 foreach 搞个缩进哈 - -->
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
<!-- TODO @puhui999下面这个就不缩进啦 - -->
#{times[1],javaType=java.time.LocalDateTime}
</select>
<select id="selectBusinessCountByDateAndEndStatus" resultType="java.lang.Long">
SELECT
COUNT(*)
FROM crm_business
WHERE deleted = 0
<if test="status != null">
AND end_status = #{status}
</if>
AND owner_user_id IN
<foreach collection="reqVO.userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{reqVO.times[0],javaType=java.time.LocalDateTime} AND
#{reqVO.times[1],javaType=java.time.LocalDateTime}
</select>
<select id="selectBusinessSummaryListGroupByEndStatus"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByEndStatusRespVO">
SELECT
end_status AS endStatus,
COUNT(*) AS businessCount,
SUM(total_price) AS totalPrice
FROM crm_business
WHERE deleted = 0 AND end_status IS NOT NULL
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
#{times[1],javaType=java.time.LocalDateTime}
GROUP BY end_status
</select>
<select id="selectBusinessSummaryGroupByDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessSummaryByDateRespVO">
SELECT
DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
COUNT(*) AS businessCreateCount,
SUM(total_price) AS totalPrice
FROM crm_business
WHERE deleted = 0
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
#{times[1],javaType=java.time.LocalDateTime}
GROUP BY time
</select>
<select id="selectBusinessInversionRateSummaryByDate"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.funnel.CrmStatisticsBusinessInversionRateSummaryByDateRespVO">
SELECT
DATE_FORMAT(create_time, '%Y-%m-%d') AS time,
COUNT(*) AS businessCount,
SUM(IF(end_status = 1, 1, 0)) AS businessWinCount
FROM crm_business
WHERE deleted = 0
AND owner_user_id IN
<foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId}
</foreach>
AND create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND
#{times[1],javaType=java.time.LocalDateTime}
GROUP BY time
</select>
</mapper>

View File

@ -5,75 +5,28 @@
<select id="selectContractCountPerformance" <select id="selectContractCountPerformance"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO"> resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO">
SELECT SELECT
t.time as time, DATE_FORMAT(order_date, '%Y%m') AS time,
COALESCE(t.currentMonthCount,0) as currentMonthCount, COUNT(1) AS currentMonthCount
COALESCE(y.lastMonthCount,0) as lastMonthCount,
COALESCE(z.lastYearCount,0) as lastYearCount
FROM
(SELECT
COUNT(1) AS currentMonthCount,
DATE_FORMAT(order_date, '%Y-%m') AS time
FROM crm_contract FROM crm_contract
WHERE deleted = 0 WHERE deleted = 0
<!-- TODO @scholar20 改成静态类引入 -->
AND audit_status = 20 AND audit_status = 20
AND owner_user_id in AND owner_user_id in
<foreach collection="userIds" item="userId" open="(" close=")" separator=","> <foreach collection="userIds" item="userId" open="(" close=")" separator=",">
#{userId} #{userId}
</foreach> </foreach>
AND DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y') <!-- TODO @scholarCrmStatisticsPerformanceReqVO 传递 year然后 java 代码里,转换出 times这样order_time 使用范围查询,避免使用函数 -->
GROUP BY time)t AND (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime}, '%Y')
LEFT JOIN or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime}, '%Y') - 1)
(SELECT GROUP BY time
COUNT(1) AS lastMonthCount,
DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 MONTH), '%Y-%m') AS time
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 (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1)
GROUP BY time)y ON t.time = y.time
LEFT JOIN
(SELECT
COUNT(1) AS lastYearCount,
DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 YEAR), '%Y-%m') AS time
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 DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1
GROUP BY time)z ON t.time = z.time
</select> </select>
<!-- TODO @scholar参考上面调整下这个 SQL 的排版、和代码建议哈 -->
<select id="selectContractPricePerformance" <select id="selectContractPricePerformance"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO"> resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO">
SELECT SELECT
t.time as time, DATE_FORMAT(order_date, '%Y%m') AS time,
COALESCE(t.currentMonthCount,0) as currentMonthCount, IFNULL(SUM(total_price), 0) AS currentMonthCount
COALESCE(y.lastMonthCount,0) as lastMonthCount,
COALESCE(z.lastYearCount,0) as lastYearCount
FROM
(SELECT
IFNULL(SUM(total_price), 0) AS currentMonthCount,
DATE_FORMAT(order_date, '%Y-%m') AS time
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 DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
GROUP BY time)t
LEFT JOIN
(SELECT
IFNULL(SUM(total_price), 0) AS lastMonthCount,
DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 MONTH), '%Y-%m') AS time
FROM crm_contract FROM crm_contract
WHERE deleted = 0 WHERE deleted = 0
AND audit_status = 20 AND audit_status = 20
@ -83,46 +36,15 @@
</foreach> </foreach>
AND (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y') AND (DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1) or DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1)
GROUP BY time)y ON t.time = y.time GROUP BY time
LEFT JOIN
(SELECT
IFNULL(SUM(total_price), 0) AS lastYearCount,
DATE_FORMAT(DATE_ADD(order_date,INTERVAL 1 YEAR), '%Y-%m') AS time
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 DATE_FORMAT(order_date, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1
GROUP BY time)z ON t.time = z.time
</select> </select>
<!-- TODO @scholar参考上面调整下这个 SQL 的排版、和代码建议哈 -->
<select id="selectReceivablePricePerformance" <select id="selectReceivablePricePerformance"
resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO"> resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.performance.CrmStatisticsPerformanceRespVO">
SELECT SELECT
t.time as time, DATE_FORMAT(return_time, '%Y%m') AS time,
COALESCE(t.currentMonthCount,0) as currentMonthCount, IFNULL(SUM(price), 0) AS currentMonthCount
COALESCE(y.lastMonthCount,0) as lastMonthCount,
COALESCE(z.lastYearCount,0) as lastYearCount
FROM
(SELECT
IFNULL(SUM(price), 0) AS currentMonthCount,
DATE_FORMAT(return_time, '%Y-%m') AS time
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 DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
GROUP BY time)t
LEFT JOIN
(SELECT
IFNULL(SUM(price), 0) AS lastMonthCount,
DATE_FORMAT(DATE_ADD(return_time,INTERVAL 1 MONTH), '%Y-%m') AS time
FROM crm_receivable FROM crm_receivable
WHERE deleted = 0 WHERE deleted = 0
AND audit_status = 20 AND audit_status = 20
@ -132,21 +54,7 @@
</foreach> </foreach>
AND (DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y') AND (DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')
or DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1) or DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1)
GROUP BY time)y ON t.time = y.time GROUP BY time
LEFT JOIN
(SELECT
IFNULL(SUM(price), 0) AS lastYearCount,
DATE_FORMAT(DATE_ADD(return_time,INTERVAL 1 YEAR), '%Y-%m') AS time
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 DATE_FORMAT(return_time, '%Y') = DATE_FORMAT(#{times[0],javaType=java.time.LocalDateTime},'%Y')-1
GROUP BY time)z ON t.time = z.time
</select> </select>
</mapper> </mapper>

View File

@ -13,8 +13,8 @@ import java.time.LocalDateTime;
* *
* @author * @author
*/ */
@TableName("infra_demo01_contact") @TableName("yudao_demo01_contact")
@KeySequence("infra_demo01_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @KeySequence("yudao_demo01_contact_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)

View File

@ -11,8 +11,8 @@ import lombok.*;
* *
* @author * @author
*/ */
@TableName("infra_demo02_category") @TableName("yudao_demo02_category")
@KeySequence("infra_demo02_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @KeySequence("yudao_demo02_category_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)

View File

@ -11,8 +11,8 @@ import lombok.*;
* *
* @author * @author
*/ */
@TableName("infra_demo03_course") @TableName("yudao_demo03_course")
@KeySequence("infra_demo03_course_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @KeySequence("yudao_demo03_course_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)

View File

@ -11,8 +11,8 @@ import lombok.*;
* *
* @author * @author
*/ */
@TableName("infra_demo03_grade") @TableName("yudao_demo03_grade")
@KeySequence("infra_demo03_grade_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @KeySequence("yudao_demo03_grade_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)

View File

@ -13,8 +13,8 @@ import java.time.LocalDateTime;
* *
* @author * @author
*/ */
@TableName("infra_demo03_student") @TableName("yudao_demo03_student")
@KeySequence("infra_demo03_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @KeySequence("yudao_demo03_student_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)

View File

@ -49,12 +49,15 @@ public class DatabaseTableServiceImpl implements DatabaseTableService {
// 使用 MyBatis Plus Generator 解析表结构 // 使用 MyBatis Plus Generator 解析表结构
DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder(config.getUrl(), config.getUsername(), DataSourceConfig dataSourceConfig = new DataSourceConfig.Builder(config.getUrl(), config.getUsername(),
config.getPassword()).build(); config.getPassword()).build();
StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder(); StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder().enableSkipView(); // 忽略视图,业务上一般用不到
if (StrUtil.isNotEmpty(name)) { if (StrUtil.isNotEmpty(name)) {
strategyConfig.addInclude(name); strategyConfig.addInclude(name);
} else { } else {
// 移除工作流和定时任务前缀的表名 // TODO 未来做成可配置 // 移除工作流和定时任务前缀的表名
strategyConfig.addExclude("ACT_[\\S\\s]+|QRTZ_[\\S\\s]+|FLW_[\\S\\s]+"); strategyConfig.addExclude("ACT_[\\S\\s]+|QRTZ_[\\S\\s]+|FLW_[\\S\\s]+");
// 移除 ORACLE 相关的系统表
strategyConfig.addExclude("IMPDP_[\\S\\s]+|ALL_[\\S\\s]+|HS_[\\S\\\\s]+");
strategyConfig.addExclude("[\\S\\s]+\\$[\\S\\s]+|[\\S\\s]+\\$"); // 表里不能有 $,一般有都是系统的表
} }
GlobalConfig globalConfig = new GlobalConfig.Builder().dateType(DateType.TIME_PACK).build(); // 只使用 LocalDateTime 类型,不使用 LocalDate GlobalConfig globalConfig = new GlobalConfig.Builder().dateType(DateType.TIME_PACK).build(); // 只使用 LocalDateTime 类型,不使用 LocalDate
ConfigBuilder builder = new ConfigBuilder(null, dataSourceConfig, strategyConfig.build(), ConfigBuilder builder = new ConfigBuilder(null, dataSourceConfig, strategyConfig.build(),

View File

@ -113,7 +113,7 @@ public class ProductCommentServiceImpl implements ProductCommentService {
// 更新可见状态 // 更新可见状态
productCommentMapper.updateById(new ProductCommentDO().setId(updateReqVO.getId()) productCommentMapper.updateById(new ProductCommentDO().setId(updateReqVO.getId())
.setVisible(true)); .setVisible(updateReqVO.getVisible()));
} }
@Override @Override

View File

@ -88,7 +88,7 @@ public class DeliveryExpressTemplateServiceImpl implements DeliveryExpressTempla
List<DeliveryExpressTemplateFreeDO> oldList = expressTemplateFreeMapper.selectListByTemplateId(templateId); List<DeliveryExpressTemplateFreeDO> oldList = expressTemplateFreeMapper.selectListByTemplateId(templateId);
List<DeliveryExpressTemplateFreeDO> newList = INSTANCE.convertTemplateFreeList(templateId, frees); List<DeliveryExpressTemplateFreeDO> newList = INSTANCE.convertTemplateFreeList(templateId, frees);
List<List<DeliveryExpressTemplateFreeDO>> diffList = CollectionUtils.diffList(oldList, newList, List<List<DeliveryExpressTemplateFreeDO>> diffList = CollectionUtils.diffList(oldList, newList,
(oldVal, newVal) -> ObjectUtil.equal(oldVal.getId(), newVal.getTemplateId())); (oldVal, newVal) -> ObjectUtil.equal(oldVal.getId(), newVal.getId()));
// 第二步,批量添加、修改、删除 // 第二步,批量添加、修改、删除
if (CollUtil.isNotEmpty(diffList.get(0))) { if (CollUtil.isNotEmpty(diffList.get(0))) {

View File

@ -18,7 +18,7 @@ public class DeptRespVO {
@Schema(description = "父部门 ID", example = "1024") @Schema(description = "父部门 ID", example = "1024")
private Long parentId; private Long parentId;
@Schema(description = "显示顺序不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer sort; private Integer sort;
@Schema(description = "负责人的用户编号", example = "2048") @Schema(description = "负责人的用户编号", example = "2048")

View File

@ -3,13 +3,12 @@ package cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Schema(description = "管理后台 - 部门创建/修改 Request VO") @Schema(description = "管理后台 - 部门创建/修改 Request VO")
@Data @Data
public class DeptSaveReqVO { public class DeptSaveReqVO {
@ -25,7 +24,7 @@ public class DeptSaveReqVO {
@Schema(description = "父部门 ID", example = "1024") @Schema(description = "父部门 ID", example = "1024")
private Long parentId; private Long parentId;
@Schema(description = "显示顺序不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "显示顺序不能为空") @NotNull(message = "显示顺序不能为空")
private Integer sort; private Integer sort;

View File

@ -27,7 +27,7 @@ public class PostRespVO {
@ExcelProperty("岗位编码") @ExcelProperty("岗位编码")
private String code; private String code;
@Schema(description = "显示顺序不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("岗位排序") @ExcelProperty("岗位排序")
private Integer sort; private Integer sort;

View File

@ -3,12 +3,11 @@ package cn.iocoder.yudao.module.system.controller.admin.dept.vo.post;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Schema(description = "管理后台 - 岗位创建/修改 Request VO") @Schema(description = "管理后台 - 岗位创建/修改 Request VO")
@Data @Data
public class PostSaveReqVO { public class PostSaveReqVO {
@ -26,7 +25,7 @@ public class PostSaveReqVO {
@Size(max = 64, message = "岗位编码长度不能超过64个字符") @Size(max = 64, message = "岗位编码长度不能超过64个字符")
private String code; private String code;
@Schema(description = "显示顺序不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "显示顺序不能为空") @NotNull(message = "显示顺序不能为空")
private Integer sort; private Integer sort;

View File

@ -4,11 +4,10 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import javax.validation.constraints.Size;
@Schema(description = "管理后台 - 字典类型分页列表 Request VO") @Schema(description = "管理后台 - 字典类型分页列表 Request VO")
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)

View File

@ -19,7 +19,7 @@ public class DictDataRespVO {
@ExcelProperty("字典编码") @ExcelProperty("字典编码")
private Long id; private Long id;
@Schema(description = "显示顺序不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("字典排序") @ExcelProperty("字典排序")
private Integer sort; private Integer sort;

View File

@ -3,12 +3,11 @@ package cn.iocoder.yudao.module.system.controller.admin.dict.vo.data;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Schema(description = "管理后台 - 字典数据创建/修改 Request VO") @Schema(description = "管理后台 - 字典数据创建/修改 Request VO")
@Data @Data
public class DictDataSaveReqVO { public class DictDataSaveReqVO {
@ -16,7 +15,7 @@ public class DictDataSaveReqVO {
@Schema(description = "字典数据编号", example = "1024") @Schema(description = "字典数据编号", example = "1024")
private Long id; private Long id;
@Schema(description = "显示顺序不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "显示顺序不能为空") @NotNull(message = "显示顺序不能为空")
private Integer sort; private Integer sort;

View File

@ -1,11 +1,11 @@
package cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu; package cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@Schema(description = "管理后台 - 菜单信息 Response VO") @Schema(description = "管理后台 - 菜单信息 Response VO")
@ -28,7 +28,7 @@ public class MenuRespVO {
@NotNull(message = "菜单类型不能为空") @NotNull(message = "菜单类型不能为空")
private Integer type; private Integer type;
@Schema(description = "显示顺序不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "显示顺序不能为空") @NotNull(message = "显示顺序不能为空")
private Integer sort; private Integer sort;

View File

@ -1,12 +1,11 @@
package cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu; package cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Schema(description = "管理后台 - 菜单创建/修改 Request VO") @Schema(description = "管理后台 - 菜单创建/修改 Request VO")
@Data @Data
public class MenuSaveVO { public class MenuSaveVO {
@ -27,7 +26,7 @@ public class MenuSaveVO {
@NotNull(message = "菜单类型不能为空") @NotNull(message = "菜单类型不能为空")
private Integer type; private Integer type;
@Schema(description = "显示顺序不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "显示顺序不能为空") @NotNull(message = "显示顺序不能为空")
private Integer sort; private Integer sort;

View File

@ -6,9 +6,9 @@ import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Set; import java.util.Set;
@ -30,7 +30,7 @@ public class RoleRespVO {
@ExcelProperty("角色标志") @ExcelProperty("角色标志")
private String code; private String code;
@Schema(description = "显示顺序不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("角色排序") @ExcelProperty("角色排序")
private Integer sort; private Integer sort;

View File

@ -2,12 +2,11 @@ package cn.iocoder.yudao.module.system.controller.admin.permission.vo.role;
import com.mzt.logapi.starter.annotation.DiffLogField; import com.mzt.logapi.starter.annotation.DiffLogField;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Schema(description = "管理后台 - 角色创建/更新 Request VO") @Schema(description = "管理后台 - 角色创建/更新 Request VO")
@Data @Data
public class RoleSaveReqVO { public class RoleSaveReqVO {
@ -27,7 +26,7 @@ public class RoleSaveReqVO {
@DiffLogField(name = "角色标志") @DiffLogField(name = "角色标志")
private String code; private String code;
@Schema(description = "显示顺序不能为空", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") @Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "显示顺序不能为空") @NotNull(message = "显示顺序不能为空")
@DiffLogField(name = "显示顺序") @DiffLogField(name = "显示顺序")
private Integer sort; private Integer sort;

View File

@ -10,15 +10,15 @@ import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum; import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
import cn.iocoder.yudao.module.system.service.tenant.TenantService; import cn.iocoder.yudao.module.system.service.tenant.TenantService;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@ -131,7 +131,7 @@ public class MenuServiceImpl implements MenuService {
@Override @Override
public List<MenuDO> getMenuList(Collection<Long> ids) { public List<MenuDO> getMenuList(Collection<Long> ids) {
// 当ids为空时返回一个空的实例对象 // 当 ids 为空时,返回一个空的实例对象
if (CollUtil.isEmpty(ids)) { if (CollUtil.isEmpty(ids)) {
return Lists.newArrayList(); return Lists.newArrayList();
} }