Commit 088fbb2096f4bf36c46d85e5057529e9b7bc3d93

Authored by YunaiV
2 parents 21452af7 cb8d4e09

Merge branch 'develop' of https://gitee.com/zhijiantianya/ruoyi-vue-pro into master-jdk21

Showing 251 changed files with 5370 additions and 5571 deletions

Too many changes.

To preserve performance only 100 of 251 files are displayed.

yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
@@ -257,11 +257,11 @@ public class CollectionUtils { @@ -257,11 +257,11 @@ public class CollectionUtils {
257 return !CollectionUtil.isEmpty(from) ? from.get(0) : null; 257 return !CollectionUtil.isEmpty(from) ? from.get(0) : null;
258 } 258 }
259 259
260 - public static <T> T findFirst(List<T> from, Predicate<T> predicate) { 260 + public static <T> T findFirst(Collection<T> from, Predicate<T> predicate) {
261 return findFirst(from, predicate, Function.identity()); 261 return findFirst(from, predicate, Function.identity());
262 } 262 }
263 263
264 - public static <T, U> U findFirst(List<T> from, Predicate<T> predicate, Function<T, U> func) { 264 + public static <T, U> U findFirst(Collection<T> from, Predicate<T> predicate, Function<T, U> func) {
265 if (CollUtil.isEmpty(from)) { 265 if (CollUtil.isEmpty(from)) {
266 return null; 266 return null;
267 } 267 }
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java
@@ -132,4 +132,40 @@ public class LocalDateTimeUtils { @@ -132,4 +132,40 @@ public class LocalDateTimeUtils {
132 return LocalDateTimeUtil.between(dateTime, LocalDateTime.now(), ChronoUnit.DAYS); 132 return LocalDateTimeUtil.between(dateTime, LocalDateTime.now(), ChronoUnit.DAYS);
133 } 133 }
134 134
  135 + /**
  136 + * 获取今天的开始时间
  137 + *
  138 + * @return 今天
  139 + */
  140 + public static LocalDateTime getToday() {
  141 + return LocalDateTimeUtil.beginOfDay(LocalDateTime.now());
  142 + }
  143 +
  144 + /**
  145 + * 获取昨天的开始时间
  146 + *
  147 + * @return 昨天
  148 + */
  149 + public static LocalDateTime getYesterday() {
  150 + return LocalDateTimeUtil.beginOfDay(LocalDateTime.now().minusDays(1));
  151 + }
  152 +
  153 + /**
  154 + * 获取本月的开始时间
  155 + *
  156 + * @return 本月
  157 + */
  158 + public static LocalDateTime getMonth() {
  159 + return beginOfMonth(LocalDateTime.now());
  160 + }
  161 +
  162 + /**
  163 + * 获取本年的开始时间
  164 + *
  165 + * @return 本年
  166 + */
  167 + public static LocalDateTime getYear() {
  168 + return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN);
  169 + }
  170 +
135 } 171 }
yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java
@@ -17,6 +17,7 @@ import java.util.Map; @@ -17,6 +17,7 @@ import java.util.Map;
17 import java.util.function.Function; 17 import java.util.function.Function;
18 18
19 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; 19 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  20 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst;
20 21
21 /** 22 /**
22 * 区域工具类 23 * 区域工具类
@@ -70,11 +71,62 @@ public class AreaUtils { @@ -70,11 +71,62 @@ public class AreaUtils {
70 * @param id 区域编号 71 * @param id 区域编号
71 * @return 区域 72 * @return 区域
72 */ 73 */
73 - public static Area getArea(Integer id) { 74 + public static Area parseArea(Integer id) {
74 return areas.get(id); 75 return areas.get(id);
75 } 76 }
76 77
77 /** 78 /**
  79 + * 获得指定区域对应的编号
  80 + *
  81 + * @param pathStr 区域路径,例如说:河南省/石家庄市/新华区
  82 + * @return 区域
  83 + */
  84 + public static Area parseArea(String pathStr) {
  85 + String[] paths = pathStr.split("/");
  86 + Area area = null;
  87 + for (String path : paths) {
  88 + if (area == null) {
  89 + area = findFirst(areas.values(), item -> item.getName().equals(path));
  90 + } else {
  91 + area = findFirst(area.getChildren(), item -> item.getName().equals(path));
  92 + }
  93 + }
  94 + return area;
  95 + }
  96 +
  97 + /**
  98 + * 获取所有节点的全路径名称如:河南省/石家庄市/新华区
  99 + *
  100 + * @param areas 地区树
  101 + * @return 所有节点的全路径名称
  102 + */
  103 + public static List<String> getAreaNodePathList(List<Area> areas) {
  104 + List<String> paths = new ArrayList<>();
  105 + areas.forEach(area -> getAreaNodePathList(area, "", paths));
  106 + return paths;
  107 + }
  108 +
  109 + /**
  110 + * 构建一棵树的所有节点的全路径名称,并将其存储为 "祖先/父级/子级" 的形式
  111 + *
  112 + * @param node 父节点
  113 + * @param path 全路径名称
  114 + * @param paths 全路径名称列表,省份/城市/地区
  115 + */
  116 + private static void getAreaNodePathList(Area node, String path, List<String> paths) {
  117 + if (node == null) {
  118 + return;
  119 + }
  120 + // 构建当前节点的路径
  121 + String currentPath = path.isEmpty() ? node.getName() : path + "/" + node.getName();
  122 + paths.add(currentPath);
  123 + // 递归遍历子节点
  124 + for (Area child : node.getChildren()) {
  125 + getAreaNodePathList(child, currentPath, paths);
  126 + }
  127 + }
  128 +
  129 + /**
78 * 格式化区域 130 * 格式化区域
79 * 131 *
80 * @param id 区域编号 132 * @param id 区域编号
@@ -88,13 +140,13 @@ public class AreaUtils { @@ -88,13 +140,13 @@ public class AreaUtils {
88 * 格式化区域 140 * 格式化区域
89 * 141 *
90 * 例如说: 142 * 例如说:
91 - * 1. id = “静安区”时:上海 上海市 静安区  
92 - * 2. id = “上海市”时:上海 上海市  
93 - * 3. id = “上海”时:上海  
94 - * 4. id = “美国”时:美国 143 + * 1. id = “静安区”时:上海 上海市 静安区
  144 + * 2. id = “上海市”时:上海 上海市
  145 + * 3. id = “上海”时:上海
  146 + * 4. id = “美国”时:美国
95 * 当区域在中国时,默认不显示中国 147 * 当区域在中国时,默认不显示中国
96 * 148 *
97 - * @param id 区域编号 149 + * @param id 区域编号
98 * @param separator 分隔符 150 * @param separator 分隔符
99 * @return 格式化后的区域 151 * @return 格式化后的区域
100 */ 152 */
@@ -141,7 +193,7 @@ public class AreaUtils { @@ -141,7 +193,7 @@ public class AreaUtils {
141 */ 193 */
142 public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) { 194 public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) {
143 for (int i = 0; i < Byte.MAX_VALUE; i++) { 195 for (int i = 0; i < Byte.MAX_VALUE; i++) {
144 - Area area = AreaUtils.getArea(id); 196 + Area area = AreaUtils.parseArea(id);
145 if (area == null) { 197 if (area == null) {
146 return null; 198 return null;
147 } 199 }
yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/IPUtils.java
@@ -72,7 +72,7 @@ public class IPUtils { @@ -72,7 +72,7 @@ public class IPUtils {
72 * @return 地区 72 * @return 地区
73 */ 73 */
74 public static Area getArea(String ip) { 74 public static Area getArea(String ip) {
75 - return AreaUtils.getArea(getAreaId(ip)); 75 + return AreaUtils.parseArea(getAreaId(ip));
76 } 76 }
77 77
78 /** 78 /**
@@ -82,6 +82,6 @@ public class IPUtils { @@ -82,6 +82,6 @@ public class IPUtils {
82 * @return 地区 82 * @return 地区
83 */ 83 */
84 public static Area getArea(long ip) { 84 public static Area getArea(long ip) {
85 - return AreaUtils.getArea(getAreaId(ip)); 85 + return AreaUtils.parseArea(getAreaId(ip));
86 } 86 }
87 } 87 }
yudao-framework/yudao-spring-boot-starter-biz-ip/src/test/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtilsTest.java
@@ -17,7 +17,7 @@ public class AreaUtilsTest { @@ -17,7 +17,7 @@ public class AreaUtilsTest {
17 @Test 17 @Test
18 public void testGetArea() { 18 public void testGetArea() {
19 // 调用:北京 19 // 调用:北京
20 - Area area = AreaUtils.getArea(110100); 20 + Area area = AreaUtils.parseArea(110100);
21 // 断言 21 // 断言
22 assertEquals(area.getId(), 110100); 22 assertEquals(area.getId(), 110100);
23 assertEquals(area.getName(), "北京市"); 23 assertEquals(area.getName(), "北京市");
yudao-framework/yudao-spring-boot-starter-excel/pom.xml
@@ -46,6 +46,11 @@ @@ -46,6 +46,11 @@
46 <groupId>com.alibaba</groupId> 46 <groupId>com.alibaba</groupId>
47 <artifactId>easyexcel</artifactId> 47 <artifactId>easyexcel</artifactId>
48 </dependency> 48 </dependency>
  49 + <dependency>
  50 + <groupId>cn.iocoder.boot</groupId>
  51 + <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
  52 + <optional>true</optional> <!-- 设置为 optional,只有在 AreaConvert 的时候使用 -->
  53 + </dependency>
49 </dependencies> 54 </dependencies>
50 55
51 </project> 56 </project>
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.excel.core.convert;
  2 +
  3 +import cn.hutool.core.convert.Convert;
  4 +import cn.iocoder.yudao.framework.ip.core.Area;
  5 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
  6 +import com.alibaba.excel.converters.Converter;
  7 +import com.alibaba.excel.enums.CellDataTypeEnum;
  8 +import com.alibaba.excel.metadata.GlobalConfiguration;
  9 +import com.alibaba.excel.metadata.data.ReadCellData;
  10 +import com.alibaba.excel.metadata.property.ExcelContentProperty;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +
  13 +/**
  14 + * Excel 数据地区转换器
  15 + *
  16 + * @author HUIHUI
  17 + */
  18 +@Slf4j
  19 +public class AreaConvert implements Converter<Object> {
  20 +
  21 + @Override
  22 + public Class<?> supportJavaTypeKey() {
  23 + throw new UnsupportedOperationException("暂不支持,也不需要");
  24 + }
  25 +
  26 + @Override
  27 + public CellDataTypeEnum supportExcelTypeKey() {
  28 + throw new UnsupportedOperationException("暂不支持,也不需要");
  29 + }
  30 +
  31 + @Override
  32 + public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty,
  33 + GlobalConfiguration globalConfiguration) {
  34 + // 解析地区编号
  35 + String label = readCellData.getStringValue();
  36 + Area area = AreaUtils.parseArea(label);
  37 + if (area == null) {
  38 + log.error("[convertToJavaData][label({}) 解析不掉]", label);
  39 + return null;
  40 + }
  41 + // 将 value 转换成对应的属性
  42 + Class<?> fieldClazz = contentProperty.getField().getType();
  43 + return Convert.convert(fieldClazz, area.getId());
  44 + }
  45 +
  46 +}
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java renamed to yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/enums/ExcelColumn.java
1 -package cn.iocoder.yudao.module.crm.enums.permission; 1 +package cn.iocoder.yudao.framework.excel.core.enums;
2 2
3 import lombok.AllArgsConstructor; 3 import lombok.AllArgsConstructor;
4 import lombok.Getter; 4 import lombok.Getter;
5 5
  6 +// TODO @puhui999:列表有办法通过 field name 么?主要考虑一个点,可能导入模版的顺序可能会变
6 /** 7 /**
7 - * Crm 数据权限角色枚举 8 + * Excel 列名枚举
  9 + * 默认枚举 26 列列名如果有需求更多的列名请自行补充
8 * 10 *
9 * @author HUIHUI 11 * @author HUIHUI
10 */ 12 */
11 @Getter 13 @Getter
12 @AllArgsConstructor 14 @AllArgsConstructor
13 -public enum CrmPermissionRoleCodeEnum { 15 +public enum ExcelColumn {
14 16
15 - CRM_ADMIN("crm_admin", "CRM 管理员"); 17 + A(0), B(1), C(2), D(3), E(4), F(5), G(6), H(7), I(8),
  18 + J(9), K(10), L(11), M(12), N(13), O(14), P(15), Q(16),
  19 + R(17), S(18), T(19), U(20), V(21), W(22), X(23), Y(24),
  20 + Z(25);
16 21
17 /** 22 /**
18 - * 角色标识 23 + * 列索引
19 */ 24 */
20 - private String code;  
21 - /**  
22 - * 角色名称  
23 - */  
24 - private String name; 25 + private final int colNum;
25 26
26 } 27 }
27 -  
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.excel.core.handler;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.lang.Assert;
  5 +import cn.iocoder.yudao.framework.common.core.KeyValue;
  6 +import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn;
  7 +import com.alibaba.excel.write.handler.SheetWriteHandler;
  8 +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
  9 +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
  10 +import org.apache.poi.hssf.usermodel.HSSFDataValidation;
  11 +import org.apache.poi.ss.usermodel.*;
  12 +import org.apache.poi.ss.util.CellRangeAddressList;
  13 +
  14 +import java.util.Comparator;
  15 +import java.util.List;
  16 +import java.util.Map;
  17 +import java.util.stream.Collectors;
  18 +
  19 +/**
  20 + * 基于固定 sheet 实现下拉框
  21 + *
  22 + * @author HUIHUI
  23 + */
  24 +public class SelectSheetWriteHandler implements SheetWriteHandler {
  25 +
  26 + /**
  27 + * 数据起始行从 0 开始
  28 + *
  29 + * 约定:本项目第一行有标题所以从 1 开始如果您的 Excel 有多行标题请自行更改
  30 + */
  31 + public static final int FIRST_ROW = 1;
  32 + /**
  33 + * 下拉列需要创建下拉框的行数,默认两千行如需更多请自行调整
  34 + */
  35 + public static final int LAST_ROW = 2000;
  36 +
  37 + private static final String DICT_SHEET_NAME = "字典sheet";
  38 +
  39 + // TODO @puhui999:Map<ExcelColumn, List<String>> 可以么?之前用 keyvalue 的原因,返回给前端,无法用 linkedhashmap,默认 key 会乱序
  40 + private final List<KeyValue<ExcelColumn, List<String>>> selectMap;
  41 +
  42 + public SelectSheetWriteHandler(List<KeyValue<ExcelColumn, List<String>>> selectMap) {
  43 + if (CollUtil.isEmpty(selectMap)) {
  44 + this.selectMap = null;
  45 + return;
  46 + }
  47 + // 校验一下 key 是否唯一
  48 + Map<String, Long> nameCounts = selectMap.stream()
  49 + .collect(Collectors.groupingBy(item -> item.getKey().name(), Collectors.counting()));
  50 + Assert.isFalse(nameCounts.entrySet().stream().allMatch(entry -> entry.getValue() > 1), "下拉数据 key 重复请排查!!!");
  51 +
  52 + selectMap.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错
  53 + this.selectMap = selectMap;
  54 + }
  55 +
  56 + @Override
  57 + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
  58 + if (CollUtil.isEmpty(selectMap)) {
  59 + return;
  60 + }
  61 +
  62 + // 1. 获取相应操作对象
  63 + DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper(); // 需要设置下拉框的 sheet 页的数据验证助手
  64 + Workbook workbook = writeWorkbookHolder.getWorkbook(); // 获得工作簿
  65 +
  66 + // 2. 创建数据字典的 sheet 页
  67 + Sheet dictSheet = workbook.createSheet(DICT_SHEET_NAME);
  68 + for (KeyValue<ExcelColumn, List<String>> keyValue : selectMap) {
  69 + int rowLength = keyValue.getValue().size();
  70 + // 2.1 设置字典 sheet 页的值 每一列一个字典项
  71 + for (int i = 0; i < rowLength; i++) {
  72 + Row row = dictSheet.getRow(i);
  73 + if (row == null) {
  74 + row = dictSheet.createRow(i);
  75 + }
  76 + row.createCell(keyValue.getKey().getColNum()).setCellValue(keyValue.getValue().get(i));
  77 + }
  78 + // 2.2 设置单元格下拉选择
  79 + setColumnSelect(writeSheetHolder, workbook, helper, keyValue);
  80 + }
  81 + }
  82 +
  83 + /**
  84 + * 设置单元格下拉选择
  85 + */
  86 + private static void setColumnSelect(WriteSheetHolder writeSheetHolder, Workbook workbook, DataValidationHelper helper,
  87 + KeyValue<ExcelColumn, List<String>> keyValue) {
  88 + // 1.1 创建可被其他单元格引用的名称
  89 + Name name = workbook.createName();
  90 + String excelColumn = keyValue.getKey().name();
  91 + // 1.2 下拉框数据来源 eg:字典sheet!$B1:$B2
  92 + String refers = DICT_SHEET_NAME + "!$" + excelColumn + "$1:$" + excelColumn + "$" + keyValue.getValue().size();
  93 + name.setNameName("dict" + keyValue.getKey()); // 设置名称的名字
  94 + name.setRefersToFormula(refers); // 设置公式
  95 +
  96 + // 2.1 设置约束
  97 + DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + keyValue.getKey()); // 设置引用约束
  98 + // 设置下拉单元格的首行、末行、首列、末列
  99 + CellRangeAddressList rangeAddressList = new CellRangeAddressList(FIRST_ROW, LAST_ROW,
  100 + keyValue.getKey().getColNum(), keyValue.getKey().getColNum());
  101 + DataValidation validation = helper.createValidation(constraint, rangeAddressList);
  102 + if (validation instanceof HSSFDataValidation) {
  103 + validation.setSuppressDropDownArrow(false);
  104 + } else {
  105 + validation.setSuppressDropDownArrow(true);
  106 + validation.setShowErrorBox(true);
  107 + }
  108 + // 2.2 阻止输入非下拉框的值
  109 + validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
  110 + validation.createErrorBox("提示", "此值不存在于下拉选择中!");
  111 + // 2.3 添加下拉框约束
  112 + writeSheetHolder.getSheet().addValidationData(validation);
  113 + }
  114 +
  115 +}
0 \ No newline at end of file 116 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
1 package cn.iocoder.yudao.framework.excel.core.util; 1 package cn.iocoder.yudao.framework.excel.core.util;
2 2
  3 +import cn.iocoder.yudao.framework.common.core.KeyValue;
  4 +import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn;
  5 +import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
3 import com.alibaba.excel.EasyExcel; 6 import com.alibaba.excel.EasyExcel;
4 import com.alibaba.excel.converters.longconverter.LongStringConverter; 7 import com.alibaba.excel.converters.longconverter.LongStringConverter;
5 import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; 8 import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
  9 +import jakarta.servlet.http.HttpServletResponse;
6 import org.springframework.web.multipart.MultipartFile; 10 import org.springframework.web.multipart.MultipartFile;
7 11
8 -import jakarta.servlet.http.HttpServletResponse;  
9 import java.io.IOException; 12 import java.io.IOException;
10 import java.net.URLEncoder; 13 import java.net.URLEncoder;
11 import java.nio.charset.StandardCharsets; 14 import java.nio.charset.StandardCharsets;
@@ -21,29 +24,46 @@ public class ExcelUtils { @@ -21,29 +24,46 @@ public class ExcelUtils {
21 /** 24 /**
22 * 将列表以 Excel 响应给前端 25 * 将列表以 Excel 响应给前端
23 * 26 *
24 - * @param response 响应  
25 - * @param filename 文件名 27 + * @param response 响应
  28 + * @param filename 文件名
26 * @param sheetName Excel sheet 名 29 * @param sheetName Excel sheet 名
27 - * @param head Excel head 头  
28 - * @param data 数据列表哦  
29 - * @param <T> 泛型,保证 head 和 data 类型的一致性 30 + * @param head Excel head 头
  31 + * @param data 数据列表哦
  32 + * @param <T> 泛型,保证 head 和 data 类型的一致性
30 * @throws IOException 写入失败的情况 33 * @throws IOException 写入失败的情况
31 */ 34 */
32 public static <T> void write(HttpServletResponse response, String filename, String sheetName, 35 public static <T> void write(HttpServletResponse response, String filename, String sheetName,
33 Class<T> head, List<T> data) throws IOException { 36 Class<T> head, List<T> data) throws IOException {
  37 + write(response, filename, sheetName, head, data, null);
  38 + }
  39 +
  40 + /**
  41 + * 将列表以 Excel 响应给前端
  42 + *
  43 + * @param response 响应
  44 + * @param filename 文件名
  45 + * @param sheetName Excel sheet 名
  46 + * @param head Excel head 头
  47 + * @param data 数据列表哦
  48 + * @param selectMap 下拉选择数据 Map<下拉所对应的列表名,下拉数据>
  49 + * @throws IOException 写入失败的情况
  50 + */
  51 + public static <T> void write(HttpServletResponse response, String filename, String sheetName,
  52 + Class<T> head, List<T> data, List<KeyValue<ExcelColumn, List<String>>> selectMap) throws IOException {
34 // 输出 Excel 53 // 输出 Excel
35 EasyExcel.write(response.getOutputStream(), head) 54 EasyExcel.write(response.getOutputStream(), head)
36 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 55 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
37 .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度 56 .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
  57 + .registerWriteHandler(new SelectSheetWriteHandler(selectMap)) // 基于固定 sheet 实现下拉框
38 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度 58 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
39 .sheet(sheetName).doWrite(data); 59 .sheet(sheetName).doWrite(data);
40 // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了 60 // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
41 - response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8)); 61 + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
42 response.setContentType("application/vnd.ms-excel;charset=UTF-8"); 62 response.setContentType("application/vnd.ms-excel;charset=UTF-8");
43 } 63 }
44 64
45 public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException { 65 public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
46 - return EasyExcel.read(file.getInputStream(), head, null) 66 + return EasyExcel.read(file.getInputStream(), head, null)
47 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 67 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
48 .doReadAllSync(); 68 .doReadAllSync();
49 } 69 }
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/listener/BpmResultListenerApi.java deleted
1 -package cn.iocoder.yudao.module.bpm.api.listener;  
2 -  
3 -import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO;  
4 -  
5 -// TODO @芋艿:后续改成支持 RPC  
6 -/**  
7 - * 业务流程实例的结果发生变化的监听器 Api  
8 - *  
9 - * @author HUIHUI  
10 - */  
11 -public interface BpmResultListenerApi {  
12 -  
13 - /**  
14 - * 监听的流程定义 Key  
15 - *  
16 - * @return 返回监听的流程定义 Key  
17 - */  
18 - String getProcessDefinitionKey();  
19 -  
20 - /**  
21 - * 处理事件  
22 - *  
23 - * @param event 事件  
24 - */  
25 - void onEvent(BpmResultListenerRespDTO event);  
26 -  
27 -}  
yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/listener/dto/BpmResultListenerRespDTO.java deleted
1 -package cn.iocoder.yudao.module.bpm.api.listener.dto;  
2 -  
3 -import lombok.Data;  
4 -  
5 -// TODO @芋艿:后续改成支持 RPC  
6 -/**  
7 - * 业务流程实例的结果 Response DTO  
8 - *  
9 - * @author HUIHUI  
10 - */  
11 -@Data  
12 -public class BpmResultListenerRespDTO {  
13 -  
14 - /**  
15 - * 流程实例的编号  
16 - */  
17 - private String id;  
18 - /**  
19 - * 流程实例的 key  
20 - */  
21 - private String processDefinitionKey;  
22 - /**  
23 - * 流程实例的结果  
24 - */  
25 - private Integer result;  
26 - /**  
27 - * 流程实例对应的业务标识  
28 - * 例如说,请假  
29 - */  
30 - private String businessKey;  
31 -  
32 -}  
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEvent.java renamed to yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/event/BpmProcessInstanceResultEvent.java
1 -package cn.iocoder.yudao.module.bpm.framework.bpm.core.event; 1 +package cn.iocoder.yudao.module.bpm.event;
2 2
3 -import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; 3 +import jakarta.validation.constraints.NotNull;
4 import lombok.Data; 4 import lombok.Data;
5 import org.springframework.context.ApplicationEvent; 5 import org.springframework.context.ApplicationEvent;
6 6
7 -import jakarta.validation.constraints.NotNull;  
8 -  
9 /** 7 /**
10 * 流程实例的结果发生变化的 Event 8 * 流程实例的结果发生变化的 Event
11 * 定位:由于额外增加了 {@link BpmProcessInstanceExtDO#getResult()} 结果,所以增加该事件 9 * 定位:由于额外增加了 {@link BpmProcessInstanceExtDO#getResult()} 结果,所以增加该事件
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventListener.java renamed to yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/event/BpmProcessInstanceResultEventListener.java
1 -package cn.iocoder.yudao.module.bpm.framework.bpm.core.event; 1 +package cn.iocoder.yudao.module.bpm.event;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import org.springframework.context.ApplicationListener; 4 import org.springframework.context.ApplicationListener;
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java
@@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI @@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI
7 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; 7 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
8 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; 8 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
9 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; 9 import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO;
10 -import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent; 10 +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent;
11 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; 11 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
12 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; 12 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
13 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 13 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventPublisher.java
1 package cn.iocoder.yudao.module.bpm.framework.bpm.core.event; 1 package cn.iocoder.yudao.module.bpm.framework.bpm.core.event;
2 2
  3 +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent;
3 import lombok.AllArgsConstructor; 4 import lombok.AllArgsConstructor;
4 import org.springframework.context.ApplicationEventPublisher; 5 import org.springframework.context.ApplicationEventPublisher;
5 import org.springframework.validation.annotation.Validated; 6 import org.springframework.validation.annotation.Validated;
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/listener/BpmServiceResultListener.java deleted
1 -package cn.iocoder.yudao.module.bpm.framework.bpm.listener;  
2 -  
3 -import cn.hutool.core.util.StrUtil;  
4 -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;  
5 -import cn.iocoder.yudao.module.bpm.api.listener.BpmResultListenerApi;  
6 -import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO;  
7 -import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent;  
8 -import jakarta.annotation.Resource;  
9 -import org.springframework.context.ApplicationListener;  
10 -import org.springframework.stereotype.Component;  
11 -  
12 -import java.util.List;  
13 -  
14 -// TODO @芋艿:后续改成支持 RPC  
15 -/**  
16 - * 业务流程结果监听器实现类  
17 - *  
18 - * @author HUIHUI  
19 - */  
20 -@Component  
21 -public class BpmServiceResultListener implements ApplicationListener<BpmProcessInstanceResultEvent> {  
22 -  
23 - @Resource  
24 - private List<BpmResultListenerApi> bpmResultListenerApis;  
25 -  
26 - @Override  
27 - public final void onApplicationEvent(BpmProcessInstanceResultEvent event) {  
28 - bpmResultListenerApis.forEach(bpmResultListenerApi -> {  
29 - if (!StrUtil.equals(event.getProcessDefinitionKey(), bpmResultListenerApi.getProcessDefinitionKey())) {  
30 - return;  
31 - }  
32 - bpmResultListenerApi.onEvent(BeanUtils.toBean(event, BpmResultListenerRespDTO.class));  
33 - });  
34 - }  
35 -  
36 -}  
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/listener/BpmOALeaveResultListener.java
1 package cn.iocoder.yudao.module.bpm.service.oa.listener; 1 package cn.iocoder.yudao.module.bpm.service.oa.listener;
2 2
3 -import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent;  
4 -import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventListener; 3 +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent;
  4 +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEventListener;
5 import cn.iocoder.yudao.module.bpm.service.oa.BpmOALeaveService; 5 import cn.iocoder.yudao.module.bpm.service.oa.BpmOALeaveService;
6 import cn.iocoder.yudao.module.bpm.service.oa.BpmOALeaveServiceImpl; 6 import cn.iocoder.yudao.module.bpm.service.oa.BpmOALeaveServiceImpl;
7 import org.springframework.stereotype.Component; 7 import org.springframework.stereotype.Component;
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java
@@ -14,5 +14,6 @@ public interface DictTypeConstants { @@ -14,5 +14,6 @@ public interface DictTypeConstants {
14 String CRM_PRODUCT_UNIT = "crm_product_unit"; // CRM 产品单位 14 String CRM_PRODUCT_UNIT = "crm_product_unit"; // CRM 产品单位
15 String CRM_PRODUCT_STATUS = "crm_product_status"; // CRM 产品状态 15 String CRM_PRODUCT_STATUS = "crm_product_status"; // CRM 产品状态
16 String CRM_FOLLOW_UP_TYPE = "crm_follow_up_type"; // CRM 跟进方式 16 String CRM_FOLLOW_UP_TYPE = "crm_follow_up_type"; // CRM 跟进方式
  17 + String CRM_RECEIVABLE_RETURN_TYPE = "crm_receivable_return_type"; // CRM 回款方式
17 18
18 } 19 }
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -11,31 +11,40 @@ public interface ErrorCodeConstants { @@ -11,31 +11,40 @@ public interface ErrorCodeConstants {
11 11
12 // ========== 合同管理 1-020-000-000 ========== 12 // ========== 合同管理 1-020-000-000 ==========
13 ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在"); 13 ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在");
14 - ErrorCode CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_000_001, "更新合同失败,原因:禁止编辑"); 14 + ErrorCode CONTRACT_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_001, "合同更新失败,原因:合同不是草稿状态");
15 ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态"); 15 ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态");
  16 + ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态");
  17 + ErrorCode CONTRACT_NO_EXISTS = new ErrorCode(1_020_000_004, "生成合同序列号重复,请重试");
16 18
17 // ========== 线索管理 1-020-001-000 ========== 19 // ========== 线索管理 1-020-001-000 ==========
18 ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在"); 20 ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
19 - ErrorCode CLUE_ANY_CLUE_NOT_EXISTS = new ErrorCode(1_020_001_001, "线索【{}】不存在");  
20 - ErrorCode CLUE_ANY_CLUE_ALREADY_TRANSLATED = new ErrorCode(1_020_001_002, "线索【{}】已经转化过了,请勿重复转化"); 21 + ErrorCode CLUE_TRANSFORM_FAIL_ALREADY = new ErrorCode(1_020_001_001, "线索已经转化过了,请勿重复转化");
21 22
22 // ========== 商机管理 1-020-002-000 ========== 23 // ========== 商机管理 1-020-002-000 ==========
23 ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在"); 24 ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在");
24 - ErrorCode BUSINESS_CONTRACT_EXISTS = new ErrorCode(1_020_002_001, "商机已关联合同,不能删除");  
25 -  
26 - // TODO @lilleo:商机状态、商机类型,都单独错误码段  
27 - 25 + ErrorCode BUSINESS_DELETE_FAIL_CONTRACT_EXISTS = new ErrorCode(1_020_002_001, "商机已关联合同,不能删除");
  26 + ErrorCode BUSINESS_UPDATE_STATUS_FAIL_END_STATUS = new ErrorCode(1_020_002_002, "更新商机状态失败,原因:已经是结束状态");
  27 + ErrorCode BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS = new ErrorCode(1_020_002_003, "更新商机状态失败,原因:已经是该状态");
28 28
29 // ========== 联系人管理 1-020-003-000 ========== 29 // ========== 联系人管理 1-020-003-000 ==========
30 ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在"); 30 ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");
31 - ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode(1_020_003_001, "联系人商机关联不存在");  
32 ErrorCode CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS = new ErrorCode(1_020_003_002, "联系人已关联合同,不能删除"); 31 ErrorCode CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS = new ErrorCode(1_020_003_002, "联系人已关联合同,不能删除");
  32 + ErrorCode CONTACT_UPDATE_OWNER_USER_FAIL = new ErrorCode(1_020_003_003, "更新联系人负责人失败");
33 33
34 // ========== 回款 1-020-004-000 ========== 34 // ========== 回款 1-020-004-000 ==========
35 ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在"); 35 ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在");
36 -  
37 - // ========== 合同管理 1-020-005-000 ========== 36 + ErrorCode RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_004_001, "更新回款失败,原因:禁止编辑");
  37 + ErrorCode RECEIVABLE_DELETE_FAIL = new ErrorCode(1_020_004_002, "删除回款失败,原因: 被回款计划所使用,不允许删除");
  38 + ErrorCode RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_004_003, "回款提交审核失败,原因:回款没处在未提交状态");
  39 + ErrorCode RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_004_004, "更新回款审核状态失败,原因:回款不是审核中状态");
  40 + ErrorCode RECEIVABLE_NO_EXISTS = new ErrorCode(1_020_004_005, "生成回款序列号重复,请重试");
  41 + ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "创建回款失败,原因:合同不是审核通过状态");
  42 + ErrorCode RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_020_004_007, "创建回款失败,原因:回款金额超出合同金额,目前剩余可退:{} 元");
  43 +
  44 + // ========== 回款计划 1-020-005-000 ==========
38 ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在"); 45 ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在");
  46 + ErrorCode RECEIVABLE_PLAN_UPDATE_FAIL = new ErrorCode(1_020_006_000, "更想回款计划失败,原因:已经有对应的还款");
  47 + ErrorCode RECEIVABLE_PLAN_EXISTS_RECEIVABLE = new ErrorCode(1_020_006_001, "回款计划已经有对应的回款,不能使用");
39 48
40 // ========== 客户管理 1_020_006_000 ========== 49 // ========== 客户管理 1_020_006_000 ==========
41 ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在"); 50 ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在");
@@ -53,14 +62,13 @@ public interface ErrorCodeConstants { @@ -53,14 +62,13 @@ public interface ErrorCodeConstants {
53 ErrorCode CUSTOMER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_020_006_012, "导入客户数据不能为空!"); 62 ErrorCode CUSTOMER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_020_006_012, "导入客户数据不能为空!");
54 ErrorCode CUSTOMER_CREATE_NAME_NOT_NULL = new ErrorCode(1_020_006_013, "客户名称不能为空!"); 63 ErrorCode CUSTOMER_CREATE_NAME_NOT_NULL = new ErrorCode(1_020_006_013, "客户名称不能为空!");
55 ErrorCode CUSTOMER_NAME_EXISTS = new ErrorCode(1_020_006_014, "已存在名为【{}】的客户!"); 64 ErrorCode CUSTOMER_NAME_EXISTS = new ErrorCode(1_020_006_014, "已存在名为【{}】的客户!");
  65 + ErrorCode CUSTOMER_UPDATE_DEAL_STATUS_FAIL = new ErrorCode(1_020_006_015, "更新客户的成交状态失败,原因:已经是该状态,无需更新");
56 66
57 // ========== 权限管理 1_020_007_000 ========== 67 // ========== 权限管理 1_020_007_000 ==========
58 ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在"); 68 ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");
59 ErrorCode CRM_PERMISSION_DENIED = new ErrorCode(1_020_007_001, "{}操作失败,原因:没有权限"); 69 ErrorCode CRM_PERMISSION_DENIED = new ErrorCode(1_020_007_001, "{}操作失败,原因:没有权限");
60 - ErrorCode CRM_PERMISSION_MODEL_NOT_EXISTS = new ErrorCode(1_020_007_002, "{}不存在");  
61 ErrorCode CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS = new ErrorCode(1_020_007_003, "{}操作失败,原因:转移对象已经是该负责人"); 70 ErrorCode CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS = new ErrorCode(1_020_007_003, "{}操作失败,原因:转移对象已经是该负责人");
62 ErrorCode CRM_PERMISSION_DELETE_FAIL = new ErrorCode(1_020_007_004, "删除数据权限失败,原因:批量删除权限的时候,只能属于同一个 bizId 下"); 71 ErrorCode CRM_PERMISSION_DELETE_FAIL = new ErrorCode(1_020_007_004, "删除数据权限失败,原因:批量删除权限的时候,只能属于同一个 bizId 下");
63 - ErrorCode CRM_PERMISSION_DELETE_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_005, "删除数据权限失败,原因:存在负责人");  
64 ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限"); 72 ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限");
65 ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人"); 73 ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人");
66 ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限"); 74 ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限");
@@ -68,6 +76,7 @@ public interface ErrorCodeConstants { @@ -68,6 +76,7 @@ public interface ErrorCodeConstants {
68 // ========== 产品 1_020_008_000 ========== 76 // ========== 产品 1_020_008_000 ==========
69 ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在"); 77 ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在");
70 ErrorCode PRODUCT_NO_EXISTS = new ErrorCode(1_020_008_001, "产品编号已存在"); 78 ErrorCode PRODUCT_NO_EXISTS = new ErrorCode(1_020_008_001, "产品编号已存在");
  79 + ErrorCode PRODUCT_NOT_ENABLE = new ErrorCode(1_020_008_002, "产品【{}】已禁用");
71 80
72 // ========== 产品分类 1_020_009_000 ========== 81 // ========== 产品分类 1_020_009_000 ==========
73 ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_020_009_000, "产品分类不存在"); 82 ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_020_009_000, "产品分类不存在");
@@ -77,21 +86,18 @@ public interface ErrorCodeConstants { @@ -77,21 +86,18 @@ public interface ErrorCodeConstants {
77 ErrorCode PRODUCT_CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1_020_009_004, "父分类不能是二级分类"); 86 ErrorCode PRODUCT_CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1_020_009_004, "父分类不能是二级分类");
78 ErrorCode product_CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_020_009_005, "存在子分类,无法删除"); 87 ErrorCode product_CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_020_009_005, "存在子分类,无法删除");
79 88
80 - // ========== 商机状态类型 1_020_010_000 ==========  
81 - ErrorCode BUSINESS_STATUS_TYPE_NOT_EXISTS = new ErrorCode(1_020_010_000, "商机状态类型不存在");  
82 - ErrorCode BUSINESS_STATUS_TYPE_NAME_EXISTS = new ErrorCode(1_020_010_001, "商机状态类型名称已存在");  
83 -  
84 - // ========== 商机状态 1_020_011_000 ==========  
85 - ErrorCode BUSINESS_STATUS_NOT_EXISTS = new ErrorCode(1_020_011_000, "商机状态不存在"); 89 + // ========== 商机状态 1_020_010_000 ==========
  90 + ErrorCode BUSINESS_STATUS_TYPE_NOT_EXISTS = new ErrorCode(1_020_010_000, "商机状态组不存在");
  91 + ErrorCode BUSINESS_STATUS_TYPE_NAME_EXISTS = new ErrorCode(1_020_010_001, "商机状态组的名称已存在");
  92 + ErrorCode BUSINESS_STATUS_UPDATE_FAIL_USED = new ErrorCode(1_020_010_002, "已经被使用的商机状态组,无法进行更新");
  93 + ErrorCode BUSINESS_STATUS_DELETE_FAIL_USED = new ErrorCode(1_020_010_002, "已经被使用的商机状态组,无法进行删除");
  94 + ErrorCode BUSINESS_STATUS_NOT_EXISTS = new ErrorCode(1_020_010_003, "商机状态不存在");
86 95
87 // ========== 客户公海规则设置 1_020_012_000 ========== 96 // ========== 客户公海规则设置 1_020_012_000 ==========
88 - ErrorCode CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_020_012_000, "客户公海配置不存在或未启用");  
89 ErrorCode CUSTOMER_LIMIT_CONFIG_NOT_EXISTS = new ErrorCode(1_020_012_001, "客户限制配置不存在"); 97 ErrorCode CUSTOMER_LIMIT_CONFIG_NOT_EXISTS = new ErrorCode(1_020_012_001, "客户限制配置不存在");
90 98
91 // ========== 跟进记录 1_020_013_000 ========== 99 // ========== 跟进记录 1_020_013_000 ==========
92 ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在"); 100 ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在");
93 ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限"); 101 ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限");
94 102
95 - // ========== 待办消息 1_020_014_000 ==========  
96 -  
97 } 103 }
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
@@ -8,19 +8,21 @@ package cn.iocoder.yudao.module.crm.enums; @@ -8,19 +8,21 @@ package cn.iocoder.yudao.module.crm.enums;
8 */ 8 */
9 public interface LogRecordConstants { 9 public interface LogRecordConstants {
10 10
11 - // ======================= CRM_LEADS 线索 =======================  
12 -  
13 - String CRM_LEADS_TYPE = "CRM 线索";  
14 - String CRM_LEADS_CREATE_SUB_TYPE = "创建线索";  
15 - String CRM_LEADS_CREATE_SUCCESS = "创建了线索{{#clue.name}}";  
16 - String CRM_LEADS_UPDATE_SUB_TYPE = "更新线索";  
17 - String CRM_LEADS_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}";  
18 - String CRM_LEADS_DELETE_SUB_TYPE = "删除线索";  
19 - String CRM_LEADS_DELETE_SUCCESS = "删除了线索【{{#clueName}}】";  
20 - String CRM_LEADS_TRANSFER_SUB_TYPE = "转移线索";  
21 - String CRM_LEADS_TRANSFER_SUCCESS = "将线索【{{#clue.name}}】的负责人从【{getAdminUserById{#clue.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";  
22 - String CRM_LEADS_TRANSLATE_SUB_TYPE = "线索转化为客户";  
23 - String CRM_LEADS_TRANSLATE_SUCCESS = "将线索【{{#clue.name}}】转化为客户"; 11 + // ======================= CRM_CLUE 线索 =======================
  12 +
  13 + String CRM_CLUE_TYPE = "CRM 线索";
  14 + String CRM_CLUE_CREATE_SUB_TYPE = "创建线索";
  15 + String CRM_CLUE_CREATE_SUCCESS = "创建了线索{{#clue.name}}";
  16 + String CRM_CLUE_UPDATE_SUB_TYPE = "更新线索";
  17 + String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}";
  18 + String CRM_CLUE_DELETE_SUB_TYPE = "删除线索";
  19 + String CRM_CLUE_DELETE_SUCCESS = "删除了线索【{{#clueName}}】";
  20 + String CRM_CLUE_TRANSFER_SUB_TYPE = "转移线索";
  21 + String CRM_CLUE_TRANSFER_SUCCESS = "将线索【{{#clue.name}}】的负责人从【{getAdminUserById{#clue.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
  22 + String CRM_CLUE_TRANSLATE_SUB_TYPE = "线索转化为客户";
  23 + String CRM_CLUE_TRANSLATE_SUCCESS = "将线索【{{#clueName}}】转化为客户";
  24 + String CRM_CLUE_FOLLOW_UP_SUB_TYPE = "线索跟进";
  25 + String CRM_CLUE_FOLLOW_UP_SUCCESS = "线索跟进【{{#clueName}}】";
24 26
25 // ======================= CRM_CUSTOMER 客户 ======================= 27 // ======================= CRM_CUSTOMER 客户 =======================
26 28
@@ -41,6 +43,10 @@ public interface LogRecordConstants { @@ -41,6 +43,10 @@ public interface LogRecordConstants {
41 String CRM_CUSTOMER_RECEIVE_SUCCESS = "{{#ownerUserName != null ? '将客户【' + #customer.name + '】分配给【' + #ownerUserName + '】' : '领取客户【' + #customer.name + '】'}}"; 43 String CRM_CUSTOMER_RECEIVE_SUCCESS = "{{#ownerUserName != null ? '将客户【' + #customer.name + '】分配给【' + #ownerUserName + '】' : '领取客户【' + #customer.name + '】'}}";
42 String CRM_CUSTOMER_IMPORT_SUB_TYPE = "{{#isUpdate ? '导入并更新客户' : '导入客户'}}"; 44 String CRM_CUSTOMER_IMPORT_SUB_TYPE = "{{#isUpdate ? '导入并更新客户' : '导入客户'}}";
43 String CRM_CUSTOMER_IMPORT_SUCCESS = "{{#isUpdate ? '导入并更新了客户【'+ #customer.name +'】' : '导入了客户【'+ #customer.name +'】'}}"; 45 String CRM_CUSTOMER_IMPORT_SUCCESS = "{{#isUpdate ? '导入并更新了客户【'+ #customer.name +'】' : '导入了客户【'+ #customer.name +'】'}}";
  46 + String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUB_TYPE = "更新客户成交状态";
  47 + String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUCCESS = "更新了客户【{{#customerName}}】的成交状态为【{{#dealStatus ? '已成交' : '未成交'}}】";
  48 + String CRM_CUSTOMER_FOLLOW_UP_SUB_TYPE = "客户跟进";
  49 + String CRM_CUSTOMER_FOLLOW_UP_SUCCESS = "客户跟进【{{#customerName}}】";
44 50
45 // ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 ======================= 51 // ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 =======================
46 52
@@ -69,6 +75,10 @@ public interface LogRecordConstants { @@ -69,6 +75,10 @@ public interface LogRecordConstants {
69 String CRM_CONTACT_DELETE_SUCCESS = "删除了联系人【{{#contactName}}】"; 75 String CRM_CONTACT_DELETE_SUCCESS = "删除了联系人【{{#contactName}}】";
70 String CRM_CONTACT_TRANSFER_SUB_TYPE = "转移联系人"; 76 String CRM_CONTACT_TRANSFER_SUB_TYPE = "转移联系人";
71 String CRM_CONTACT_TRANSFER_SUCCESS = "将联系人【{{#contact.name}}】的负责人从【{getAdminUserById{#contact.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; 77 String CRM_CONTACT_TRANSFER_SUCCESS = "将联系人【{{#contact.name}}】的负责人从【{getAdminUserById{#contact.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
  78 + String CRM_CONTACT_FOLLOW_UP_SUB_TYPE = "联系人跟进";
  79 + String CRM_CONTACT_FOLLOW_UP_SUCCESS = "联系人跟进【{{#contactName}}】";
  80 + String CRM_CONTACT_UPDATE_OWNER_USER_SUB_TYPE = "更新联系人负责人";
  81 + String CRM_CONTACT_UPDATE_OWNER_USER_SUCCESS = "将联系人【{{#contact.name}}】的负责人从【{getAdminUserById{#contact.ownerUserId}}】变更为了【{getAdminUserById{#ownerUserId}}】";
72 82
73 // ======================= CRM_BUSINESS 商机 ======================= 83 // ======================= CRM_BUSINESS 商机 =======================
74 84
@@ -81,6 +91,16 @@ public interface LogRecordConstants { @@ -81,6 +91,16 @@ public interface LogRecordConstants {
81 String CRM_BUSINESS_DELETE_SUCCESS = "删除了商机【{{#businessName}}】"; 91 String CRM_BUSINESS_DELETE_SUCCESS = "删除了商机【{{#businessName}}】";
82 String CRM_BUSINESS_TRANSFER_SUB_TYPE = "转移商机"; 92 String CRM_BUSINESS_TRANSFER_SUB_TYPE = "转移商机";
83 String CRM_BUSINESS_TRANSFER_SUCCESS = "将商机【{{#business.name}}】的负责人从【{getAdminUserById{#business.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; 93 String CRM_BUSINESS_TRANSFER_SUCCESS = "将商机【{{#business.name}}】的负责人从【{getAdminUserById{#business.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
  94 + String CRM_BUSINESS_FOLLOW_UP_SUB_TYPE = "商机跟进";
  95 + String CRM_BUSINESS_FOLLOW_UP_SUCCESS = "商机跟进【{{#businessName}}】";
  96 + String CRM_BUSINESS_UPDATE_STATUS_SUB_TYPE = "更新商机状态";
  97 + String CRM_BUSINESS_UPDATE_STATUS_SUCCESS = "更新了商机【{{#businessName}}】的状态从【{{#oldStatusName}}】变更为了【{{#newStatusName}}】";
  98 +
  99 + // ======================= CRM_CONTRACT_CONFIG 合同配置 =======================
  100 +
  101 + String CRM_CONTRACT_CONFIG_TYPE = "CRM 合同配置";
  102 + String CRM_CONTRACT_CONFIG_SUB_TYPE = "{{#isPoolConfigUpdate ? '更新合同配置' : '创建合同配置'}}";
  103 + String CRM_CONTRACT_CONFIG_SUCCESS = "{{#isPoolConfigUpdate ? '更新了合同配置' : '创建了合同配置'}}";
84 104
85 // ======================= CRM_CONTRACT 合同 ======================= 105 // ======================= CRM_CONTRACT 合同 =======================
86 106
@@ -95,6 +115,8 @@ public interface LogRecordConstants { @@ -95,6 +115,8 @@ public interface LogRecordConstants {
95 String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{getAdminUserById{#contract.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; 115 String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{getAdminUserById{#contract.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
96 String CRM_CONTRACT_SUBMIT_SUB_TYPE = "提交合同审批"; 116 String CRM_CONTRACT_SUBMIT_SUB_TYPE = "提交合同审批";
97 String CRM_CONTRACT_SUBMIT_SUCCESS = "提交合同【{{#contractName}}】审批成功"; 117 String CRM_CONTRACT_SUBMIT_SUCCESS = "提交合同【{{#contractName}}】审批成功";
  118 + String CRM_CONTRACT_FOLLOW_UP_SUB_TYPE = "合同跟进";
  119 + String CRM_CONTRACT_FOLLOW_UP_SUCCESS = "合同跟进【{{#contractName}}】";
98 120
99 // ======================= CRM_PRODUCT 产品 ======================= 121 // ======================= CRM_PRODUCT 产品 =======================
100 122
@@ -125,6 +147,8 @@ public interface LogRecordConstants { @@ -125,6 +147,8 @@ public interface LogRecordConstants {
125 String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}"; 147 String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}";
126 String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款"; 148 String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款";
127 String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款"; 149 String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款";
  150 + String CRM_RECEIVABLE_SUBMIT_SUB_TYPE = "提交回款审批";
  151 + String CRM_RECEIVABLE_SUBMIT_SUCCESS = "提交编号为【{{#receivableNo}}】的回款审批成功";
128 152
129 // ======================= CRM_RECEIVABLE_PLAN 回款计划 ======================= 153 // ======================= CRM_RECEIVABLE_PLAN 回款计划 =======================
130 154
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java renamed to yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBusinessEndStatusEnum.java
1 package cn.iocoder.yudao.module.crm.enums.business; 1 package cn.iocoder.yudao.module.crm.enums.business;
2 2
3 -import cn.hutool.core.util.ObjectUtil;  
4 import cn.iocoder.yudao.framework.common.core.IntArrayValuable; 3 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
5 import lombok.Getter; 4 import lombok.Getter;
6 import lombok.RequiredArgsConstructor; 5 import lombok.RequiredArgsConstructor;
7 6
8 import java.util.Arrays; 7 import java.util.Arrays;
9 8
10 -// TODO @lzxhqs:1)title、description、create 可以删除,非标准的 javadoc 注释哈,然后可以在类上加下这个类的注释;2)CrmBizEndStatus 改成 CrmBusinessEndStatus,非必要不缩写哈,可阅读比较重要  
11 /** 9 /**
  10 + * 商机的结束状态枚举
  11 + *
12 * @author lzxhqs 12 * @author lzxhqs
13 - * @version 1.0  
14 - * @title CrmBizEndStatus  
15 - * @description  
16 - * @create 2024/1/12  
17 */ 13 */
18 @RequiredArgsConstructor 14 @RequiredArgsConstructor
19 @Getter 15 @Getter
20 -public enum CrmBizEndStatus implements IntArrayValuable { 16 +public enum CrmBusinessEndStatusEnum implements IntArrayValuable {
21 17
22 WIN(1, "赢单"), 18 WIN(1, "赢单"),
23 LOSE(2, "输单"), 19 LOSE(2, "输单"),
24 INVALID(3, "无效"); 20 INVALID(3, "无效");
25 21
26 - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmBizEndStatus::getStatus).toArray();  
27 -  
28 - // TODO @lzxhqs:这里的方法,建议放到 49 行之后;一般类里是,静态变量,普通变量;静态方法;普通方法  
29 - public static boolean isWin(Integer status) {  
30 - return ObjectUtil.equal(WIN.getStatus(), status);  
31 - }  
32 -  
33 - public static boolean isLose(Integer status) {  
34 - return ObjectUtil.equal(LOSE.getStatus(), status);  
35 - }  
36 -  
37 - public static boolean isInvalid(Integer status) {  
38 - return ObjectUtil.equal(INVALID.getStatus(), status);  
39 - } 22 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmBusinessEndStatusEnum::getStatus).toArray();
40 23
41 /** 24 /**
42 * 场景类型 25 * 场景类型
@@ -52,4 +35,11 @@ public enum CrmBizEndStatus implements IntArrayValuable { @@ -52,4 +35,11 @@ public enum CrmBizEndStatus implements IntArrayValuable {
52 return ARRAYS; 35 return ARRAYS;
53 } 36 }
54 37
  38 + public static CrmBusinessEndStatusEnum fromStatus(Integer status) {
  39 + return Arrays.stream(values())
  40 + .filter(value -> value.getStatus().equals(status))
  41 + .findFirst()
  42 + .orElse(null);
  43 + }
  44 +
55 } 45 }
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java
@@ -17,7 +17,7 @@ import java.util.Arrays; @@ -17,7 +17,7 @@ import java.util.Arrays;
17 @Getter 17 @Getter
18 public enum CrmBizTypeEnum implements IntArrayValuable { 18 public enum CrmBizTypeEnum implements IntArrayValuable {
19 19
20 - CRM_LEADS(1, "线索"), 20 + CRM_CLUE(1, "线索"),
21 CRM_CUSTOMER(2, "客户"), 21 CRM_CUSTOMER(2, "客户"),
22 CRM_CONTACT(3, "联系人"), 22 CRM_CONTACT(3, "联系人"),
23 CRM_BUSINESS(4, "商机"), 23 CRM_BUSINESS(4, "商机"),
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionLevelEnum.java
@@ -10,6 +10,8 @@ import java.util.Arrays; @@ -10,6 +10,8 @@ import java.util.Arrays;
10 /** 10 /**
11 * CRM 数据权限级别枚举 11 * CRM 数据权限级别枚举
12 * 12 *
  13 + * OWNER > WRITE > READ
  14 + *
13 * @author HUIHUI 15 * @author HUIHUI
14 */ 16 */
15 @Getter 17 @Getter
@@ -17,8 +19,8 @@ import java.util.Arrays; @@ -17,8 +19,8 @@ import java.util.Arrays;
17 public enum CrmPermissionLevelEnum implements IntArrayValuable { 19 public enum CrmPermissionLevelEnum implements IntArrayValuable {
18 20
19 OWNER(1, "负责人"), 21 OWNER(1, "负责人"),
20 - READ(2, "读"),  
21 - WRITE(3, "写"); 22 + READ(2, "只读"),
  23 + WRITE(3, "读写");
22 24
23 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmPermissionLevelEnum::getLevel).toArray(); 25 public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmPermissionLevelEnum::getLevel).toArray();
24 26
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/receivable/CrmReceivableReturnTypeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.enums.receivable;
  2 +
  3 +import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
  4 +import lombok.AllArgsConstructor;
  5 +import lombok.Getter;
  6 +
  7 +import java.util.Arrays;
  8 +
  9 +/**
  10 + * CRM 回款方式枚举
  11 + *
  12 + * @author HUIHUI
  13 + */
  14 +@Getter
  15 +@AllArgsConstructor
  16 +public enum CrmReceivableReturnTypeEnum implements IntArrayValuable {
  17 +
  18 + CHECK(1, "支票"),
  19 + CASH(2, "现金"),
  20 + POSTAL_REMITTANCE(3, "邮政汇款"),
  21 + TELEGRAPHIC_TRANSFER(4, "电汇"),
  22 + ONLINE_TRANSFER(5, "网上转账"),
  23 + ALIPAY(6, "支付宝"),
  24 + WECHAT_PAY(7, "微信支付"),
  25 + OTHER(8, "其它");
  26 +
  27 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmReceivableReturnTypeEnum::getType).toArray();
  28 +
  29 + /**
  30 + * 类型
  31 + */
  32 + private final Integer type;
  33 + /**
  34 + * 名称
  35 + */
  36 + private final String name;
  37 +
  38 + @Override
  39 + public int[] array() {
  40 + return ARRAYS;
  41 + }
  42 +
  43 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/CrmBacklogController.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.backlog;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.CommonResult;  
4 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
5 -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;  
6 -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerRespVO;  
7 -import cn.iocoder.yudao.module.crm.controller.admin.backlog.vo.CrmTodayCustomerPageReqVO;  
8 -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;  
9 -import cn.iocoder.yudao.module.crm.service.message.CrmBacklogService;  
10 -import io.swagger.v3.oas.annotations.Operation;  
11 -import io.swagger.v3.oas.annotations.tags.Tag;  
12 -import jakarta.annotation.Resource;  
13 -import jakarta.validation.Valid;  
14 -import org.springframework.security.access.prepost.PreAuthorize;  
15 -import org.springframework.validation.annotation.Validated;  
16 -import org.springframework.web.bind.annotation.GetMapping;  
17 -import org.springframework.web.bind.annotation.RequestMapping;  
18 -import org.springframework.web.bind.annotation.RestController;  
19 -  
20 -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;  
21 -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;  
22 -  
23 -@Tag(name = "管理后台 - CRM待办消息")  
24 -@RestController  
25 -@RequestMapping("/crm/backlog")  
26 -@Validated  
27 -public class CrmBacklogController {  
28 -  
29 - @Resource  
30 - private CrmBacklogService crmMessageService;  
31 -  
32 - // TODO 芋艿:未来可能合并到 CrmCustomerController  
33 - @GetMapping("/today-customer-page")  
34 - @Operation(summary = "今日需联系客户")  
35 - @PreAuthorize("@ss.hasPermission('crm:customer:query')")  
36 - public CommonResult<PageResult<CrmCustomerRespVO>> getTodayCustomerPage(@Valid CrmTodayCustomerPageReqVO pageReqVO) {  
37 - PageResult<CrmCustomerDO> pageResult = crmMessageService.getTodayCustomerPage(pageReqVO, getLoginUserId());  
38 - return success(BeanUtils.toBean(pageResult, CrmCustomerRespVO.class));  
39 - }  
40 -  
41 -}  
42 \ No newline at end of file 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/vo/CrmTodayCustomerPageReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.backlog.vo;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.PageParam;  
4 -import cn.iocoder.yudao.framework.common.validation.InEnum;  
5 -import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;  
6 -import io.swagger.v3.oas.annotations.media.Schema;  
7 -import lombok.Data;  
8 -import lombok.EqualsAndHashCode;  
9 -import lombok.ToString;  
10 -  
11 -@Schema(description = "管理后台 - 今日需联系客户 Request VO")  
12 -@Data  
13 -@EqualsAndHashCode(callSuper = true)  
14 -@ToString(callSuper = true)  
15 -public class CrmTodayCustomerPageReqVO extends PageParam {  
16 -  
17 - /**  
18 - * 联系状态 - 今日需联系  
19 - */  
20 - public static final int CONTACT_TODAY = 1;  
21 - /**  
22 - * 联系状态 - 已逾期  
23 - */  
24 - public static final int CONTACT_EXPIRED = 2;  
25 - /**  
26 - * 联系状态 - 已联系  
27 - */  
28 - public static final int CONTACT_ALREADY = 3;  
29 -  
30 - @Schema(description = "联系状态", example = "1")  
31 - private Integer contactStatus;  
32 -  
33 - @Schema(description = "场景类型", example = "1")  
34 - @InEnum(CrmSceneTypeEnum.class)  
35 - private Integer sceneType;  
36 -  
37 -}  
38 \ No newline at end of file 0 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.http deleted
1 -### 请求 /transfer  
2 -PUT {{baseUrl}}/crm/business/transfer  
3 -Content-Type: application/json  
4 -Authorization: Bearer {{token}}  
5 -tenant-id: {{adminTenentId}}  
6 -  
7 -{  
8 - "id": 1,  
9 - "ownerUserId": 2,  
10 - "transferType": 2,  
11 - "permissionType": 2  
12 -}  
13 -  
14 -### 请求 /update  
15 -PUT {{baseUrl}}/crm/business/update  
16 -Content-Type: application/json  
17 -Authorization: Bearer {{token}}  
18 -tenant-id: {{adminTenentId}}  
19 -  
20 -{  
21 - "id": 1,  
22 - "name": "2",  
23 - "statusTypeId": 2,  
24 - "statusId": 2,  
25 - "customerId": 1  
26 -}  
27 -  
28 -### 请求 /get  
29 -GET {{baseUrl}}/crm/business/get?id=1024  
30 -Content-Type: application/json  
31 -Authorization: Bearer {{token}}  
32 -tenant-id: {{adminTenentId}}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
@@ -3,22 +3,26 @@ package cn.iocoder.yudao.module.crm.controller.admin.business; @@ -3,22 +3,26 @@ package cn.iocoder.yudao.module.crm.controller.admin.business;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
8 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
9 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO;  
10 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;  
11 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO;  
12 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO;  
13 -import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert; 11 +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*;
14 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; 12 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  13 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
15 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; 14 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
16 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; 15 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
17 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
  17 +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
18 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; 18 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
19 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService; 19 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
20 -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusTypeService;  
21 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; 20 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
  21 +import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
  22 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  23 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  24 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  25 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
22 import io.swagger.v3.oas.annotations.Operation; 26 import io.swagger.v3.oas.annotations.Operation;
23 import io.swagger.v3.oas.annotations.Parameter; 27 import io.swagger.v3.oas.annotations.Parameter;
24 import io.swagger.v3.oas.annotations.tags.Tag; 28 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -30,13 +34,15 @@ import org.springframework.validation.annotation.Validated; @@ -30,13 +34,15 @@ import org.springframework.validation.annotation.Validated;
30 import org.springframework.web.bind.annotation.*; 34 import org.springframework.web.bind.annotation.*;
31 35
32 import java.io.IOException; 36 import java.io.IOException;
  37 +import java.util.Collections;
33 import java.util.List; 38 import java.util.List;
  39 +import java.util.Map;
  40 +import java.util.stream.Stream;
34 41
35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
36 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 43 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
37 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; 44 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
38 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;  
39 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 45 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
40 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; 46 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
41 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 47 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
42 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS; 48 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS;
@@ -52,9 +58,16 @@ public class CrmBusinessController { @@ -52,9 +58,16 @@ public class CrmBusinessController {
52 @Resource 58 @Resource
53 private CrmCustomerService customerService; 59 private CrmCustomerService customerService;
54 @Resource 60 @Resource
55 - private CrmBusinessStatusTypeService businessStatusTypeService; 61 + private CrmBusinessStatusService businessStatusTypeService;
56 @Resource 62 @Resource
57 private CrmBusinessStatusService businessStatusService; 63 private CrmBusinessStatusService businessStatusService;
  64 + @Resource
  65 + private CrmProductService productService;
  66 +
  67 + @Resource
  68 + private AdminUserApi adminUserApi;
  69 + @Resource
  70 + private DeptApi deptApi;
58 71
59 @PostMapping("/create") 72 @PostMapping("/create")
60 @Operation(summary = "创建商机") 73 @Operation(summary = "创建商机")
@@ -71,6 +84,14 @@ public class CrmBusinessController { @@ -71,6 +84,14 @@ public class CrmBusinessController {
71 return success(true); 84 return success(true);
72 } 85 }
73 86
  87 + @PutMapping("/update-status")
  88 + @Operation(summary = "更新商机状态")
  89 + @PreAuthorize("@ss.hasPermission('crm:business:update')")
  90 + public CommonResult<Boolean> updateBusinessStatus(@Valid @RequestBody CrmBusinessUpdateStatusReqVO updateStatusReqVO) {
  91 + businessService.updateBusinessStatus(updateStatusReqVO);
  92 + return success(true);
  93 + }
  94 +
74 @DeleteMapping("/delete") 95 @DeleteMapping("/delete")
75 @Operation(summary = "删除商机") 96 @Operation(summary = "删除商机")
76 @Parameter(name = "id", description = "编号", required = true) 97 @Parameter(name = "id", description = "编号", required = true)
@@ -86,15 +107,23 @@ public class CrmBusinessController { @@ -86,15 +107,23 @@ public class CrmBusinessController {
86 @PreAuthorize("@ss.hasPermission('crm:business:query')") 107 @PreAuthorize("@ss.hasPermission('crm:business:query')")
87 public CommonResult<CrmBusinessRespVO> getBusiness(@RequestParam("id") Long id) { 108 public CommonResult<CrmBusinessRespVO> getBusiness(@RequestParam("id") Long id) {
88 CrmBusinessDO business = businessService.getBusiness(id); 109 CrmBusinessDO business = businessService.getBusiness(id);
89 - return success(BeanUtils.toBean(business, CrmBusinessRespVO.class)); 110 + return success(buildBusinessDetail(business));
90 } 111 }
91 112
92 - @GetMapping("/list-by-ids")  
93 - @Operation(summary = "获得商机列表")  
94 - @Parameter(name = "ids", description = "编号", required = true, example = "[1024]")  
95 - @PreAuthorize("@ss.hasPermission('crm:business:query')")  
96 - public CommonResult<List<CrmBusinessRespVO>> getContactListByIds(@RequestParam("ids") List<Long> ids) {  
97 - return success(BeanUtils.toBean(businessService.getBusinessList(ids, getLoginUserId()), CrmBusinessRespVO.class)); 113 + private CrmBusinessRespVO buildBusinessDetail(CrmBusinessDO business) {
  114 + if (business == null) {
  115 + return null;
  116 + }
  117 + CrmBusinessRespVO businessVO = buildBusinessDetailList(Collections.singletonList(business)).get(0);
  118 + // 拼接产品项
  119 + List<CrmBusinessProductDO> businessProducts = businessService.getBusinessProductListByBusinessId(businessVO.getId());
  120 + Map<Long, CrmProductDO> productMap = productService.getProductMap(
  121 + convertSet(businessProducts, CrmBusinessProductDO::getProductId));
  122 + businessVO.setProducts(BeanUtils.toBean(businessProducts, CrmBusinessRespVO.Product.class, businessProductVO ->
  123 + MapUtils.findAndThen(productMap, businessProductVO.getProductId(),
  124 + product -> businessProductVO.setProductName(product.getName())
  125 + .setProductNo(product.getNo()).setProductUnit(product.getUnit()))));
  126 + return businessVO;
98 } 127 }
99 128
100 @GetMapping("/simple-all-list") 129 @GetMapping("/simple-all-list")
@@ -105,7 +134,8 @@ public class CrmBusinessController { @@ -105,7 +134,8 @@ public class CrmBusinessController {
105 reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 134 reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
106 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(reqVO, getLoginUserId()); 135 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(reqVO, getLoginUserId());
107 return success(convertList(pageResult.getList(), business -> // 只返回 id、name 字段 136 return success(convertList(pageResult.getList(), business -> // 只返回 id、name 字段
108 - new CrmBusinessRespVO().setId(business.getId()).setName(business.getName()))); 137 + new CrmBusinessRespVO().setId(business.getId()).setName(business.getName())
  138 + .setCustomerId(business.getCustomerId())));
109 } 139 }
110 140
111 @GetMapping("/page") 141 @GetMapping("/page")
@@ -113,7 +143,7 @@ public class CrmBusinessController { @@ -113,7 +143,7 @@ public class CrmBusinessController {
113 @PreAuthorize("@ss.hasPermission('crm:business:query')") 143 @PreAuthorize("@ss.hasPermission('crm:business:query')")
114 public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) { 144 public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) {
115 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, getLoginUserId()); 145 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(pageVO, getLoginUserId());
116 - return success(buildBusinessDetailPageResult(pageResult)); 146 + return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
117 } 147 }
118 148
119 @GetMapping("/page-by-customer") 149 @GetMapping("/page-by-customer")
@@ -123,7 +153,7 @@ public class CrmBusinessController { @@ -123,7 +153,7 @@ public class CrmBusinessController {
123 throw exception(CUSTOMER_NOT_EXISTS); 153 throw exception(CUSTOMER_NOT_EXISTS);
124 } 154 }
125 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByCustomerId(pageReqVO); 155 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByCustomerId(pageReqVO);
126 - return success(buildBusinessDetailPageResult(pageResult)); 156 + return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
127 } 157 }
128 158
129 @GetMapping("/page-by-contact") 159 @GetMapping("/page-by-contact")
@@ -131,7 +161,7 @@ public class CrmBusinessController { @@ -131,7 +161,7 @@ public class CrmBusinessController {
131 @PreAuthorize("@ss.hasPermission('crm:business:query')") 161 @PreAuthorize("@ss.hasPermission('crm:business:query')")
132 public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessContactPage(@Valid CrmBusinessPageReqVO pageReqVO) { 162 public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessContactPage(@Valid CrmBusinessPageReqVO pageReqVO) {
133 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByContact(pageReqVO); 163 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPageByContact(pageReqVO);
134 - return success(buildBusinessDetailPageResult(pageResult)); 164 + return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal()));
135 } 165 }
136 166
137 @GetMapping("/export-excel") 167 @GetMapping("/export-excel")
@@ -141,29 +171,44 @@ public class CrmBusinessController { @@ -141,29 +171,44 @@ public class CrmBusinessController {
141 public void exportBusinessExcel(@Valid CrmBusinessPageReqVO exportReqVO, 171 public void exportBusinessExcel(@Valid CrmBusinessPageReqVO exportReqVO,
142 HttpServletResponse response) throws IOException { 172 HttpServletResponse response) throws IOException {
143 exportReqVO.setPageSize(PAGE_SIZE_NONE); 173 exportReqVO.setPageSize(PAGE_SIZE_NONE);
144 - PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(exportReqVO, getLoginUserId()); 174 + List<CrmBusinessDO> list = businessService.getBusinessPage(exportReqVO, getLoginUserId()).getList();
145 // 导出 Excel 175 // 导出 Excel
146 ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessRespVO.class, 176 ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessRespVO.class,
147 - buildBusinessDetailPageResult(pageResult).getList()); 177 + buildBusinessDetailList(list));
148 } 178 }
149 179
150 - /**  
151 - * 构建详细的商机分页结果  
152 - *  
153 - * @param pageResult 简单的商机分页结果  
154 - * @return 详细的商机分页结果  
155 - */  
156 - private PageResult<CrmBusinessRespVO> buildBusinessDetailPageResult(PageResult<CrmBusinessDO> pageResult) {  
157 - if (CollUtil.isEmpty(pageResult.getList())) {  
158 - return PageResult.empty(pageResult.getTotal()); 180 + private List<CrmBusinessRespVO> buildBusinessDetailList(List<CrmBusinessDO> list) {
  181 + if (CollUtil.isEmpty(list)) {
  182 + return Collections.emptyList();
159 } 183 }
160 - List<CrmBusinessStatusTypeDO> statusTypeList = businessStatusTypeService.getBusinessStatusTypeList(  
161 - convertSet(pageResult.getList(), CrmBusinessDO::getStatusTypeId));  
162 - List<CrmBusinessStatusDO> statusList = businessStatusService.getBusinessStatusList(  
163 - convertSet(pageResult.getList(), CrmBusinessDO::getStatusId));  
164 - List<CrmCustomerDO> customerList = customerService.getCustomerList(  
165 - convertSet(pageResult.getList(), CrmBusinessDO::getCustomerId));  
166 - return CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList); 184 + // 1.1 获取客户列表
  185 + Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
  186 + convertSet(list, CrmBusinessDO::getCustomerId));
  187 + // 1.2 获取创建人、负责人列表
  188 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
  189 + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
  190 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  191 + // 1.3 获得商机状态组
  192 + Map<Long, CrmBusinessStatusTypeDO> statusTypeMap = businessStatusTypeService.getBusinessStatusTypeMap(
  193 + convertSet(list, CrmBusinessDO::getStatusTypeId));
  194 + Map<Long, CrmBusinessStatusDO> statusMap = businessStatusService.getBusinessStatusMap(
  195 + convertSet(list, CrmBusinessDO::getStatusId));
  196 + // 2. 拼接数据
  197 + return BeanUtils.toBean(list, CrmBusinessRespVO.class, businessVO -> {
  198 + // 2.1 设置客户名称
  199 + MapUtils.findAndThen(customerMap, businessVO.getCustomerId(), customer -> businessVO.setCustomerName(customer.getName()));
  200 + // 2.2 设置创建人、负责人名称
  201 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(businessVO.getCreator()),
  202 + user -> businessVO.setCreatorName(user.getNickname()));
  203 + MapUtils.findAndThen(userMap, businessVO.getOwnerUserId(), user -> {
  204 + businessVO.setOwnerUserName(user.getNickname());
  205 + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> businessVO.setOwnerUserDeptName(dept.getName()));
  206 + });
  207 + // 2.3 设置商机状态
  208 + MapUtils.findAndThen(statusTypeMap, businessVO.getStatusTypeId(), statusType -> businessVO.setStatusTypeName(statusType.getName()));
  209 + MapUtils.findAndThen(statusMap, businessVO.getStatusId(), status -> businessVO.setStatusName(
  210 + businessService.getBusinessStatusName(businessVO.getEndStatus(), status)));
  211 + });
167 } 212 }
168 213
169 @PutMapping("/transfer") 214 @PutMapping("/transfer")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.business;
  2 +
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  5 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO;
  10 +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO;
  11 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
  12 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
  13 +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
  14 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  15 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  16 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  17 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  18 +import io.swagger.v3.oas.annotations.Operation;
  19 +import io.swagger.v3.oas.annotations.Parameter;
  20 +import io.swagger.v3.oas.annotations.tags.Tag;
  21 +import jakarta.annotation.Resource;
  22 +import jakarta.validation.Valid;
  23 +import org.springframework.security.access.prepost.PreAuthorize;
  24 +import org.springframework.validation.annotation.Validated;
  25 +import org.springframework.web.bind.annotation.*;
  26 +
  27 +import java.util.Collection;
  28 +import java.util.List;
  29 +import java.util.Map;
  30 +
  31 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  32 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  33 +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  34 +
  35 +@Tag(name = "管理后台 - CRM 商机状态")
  36 +@RestController
  37 +@RequestMapping("/crm/business-status")
  38 +@Validated
  39 +public class CrmBusinessStatusController {
  40 +
  41 + @Resource
  42 + private CrmBusinessStatusService businessStatusTypeService;
  43 +
  44 + @Resource
  45 + private AdminUserApi adminUserApi;
  46 + @Resource
  47 + private DeptApi deptApi;
  48 +
  49 + @PostMapping("/create")
  50 + @Operation(summary = "创建商机状态")
  51 + @PreAuthorize("@ss.hasPermission('crm:business-status:create')")
  52 + public CommonResult<Long> createBusinessStatus(@Valid @RequestBody CrmBusinessStatusSaveReqVO createReqVO) {
  53 + return success(businessStatusTypeService.createBusinessStatus(createReqVO));
  54 + }
  55 +
  56 + @PutMapping("/update")
  57 + @Operation(summary = "更新商机状态")
  58 + @PreAuthorize("@ss.hasPermission('crm:business-status:update')")
  59 + public CommonResult<Boolean> updateBusinessStatus(@Valid @RequestBody CrmBusinessStatusSaveReqVO updateReqVO) {
  60 + businessStatusTypeService.updateBusinessStatus(updateReqVO);
  61 + return success(true);
  62 + }
  63 +
  64 + @DeleteMapping("/delete")
  65 + @Operation(summary = "删除商机状态")
  66 + @Parameter(name = "id", description = "编号", required = true)
  67 + @PreAuthorize("@ss.hasPermission('crm:business-status:delete')")
  68 + public CommonResult<Boolean> deleteBusinessStatusType(@RequestParam("id") Long id) {
  69 + businessStatusTypeService.deleteBusinessStatusType(id);
  70 + return success(true);
  71 + }
  72 +
  73 + @GetMapping("/get")
  74 + @Operation(summary = "获得商机状态")
  75 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  76 + @PreAuthorize("@ss.hasPermission('crm:business-status:query')")
  77 + public CommonResult<CrmBusinessStatusRespVO> getBusinessStatusType(@RequestParam("id") Long id) {
  78 + CrmBusinessStatusTypeDO statusType = businessStatusTypeService.getBusinessStatusType(id);
  79 + if (statusType == null) {
  80 + return success(null);
  81 + }
  82 + List<CrmBusinessStatusDO> statuses = businessStatusTypeService.getBusinessStatusListByTypeId(id);
  83 + return success(BeanUtils.toBean(statusType, CrmBusinessStatusRespVO.class,
  84 + statusTypeVO -> statusTypeVO.setStatuses(BeanUtils.toBean(statuses, CrmBusinessStatusRespVO.Status.class))));
  85 + }
  86 +
  87 + @GetMapping("/page")
  88 + @Operation(summary = "获得商机状态分页")
  89 + @PreAuthorize("@ss.hasPermission('crm:business-status:query')")
  90 + public CommonResult<PageResult<CrmBusinessStatusRespVO>> getBusinessStatusPage(@Valid PageParam pageReqVO) {
  91 + // 1. 查询数据
  92 + PageResult<CrmBusinessStatusTypeDO> pageResult = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO);
  93 + if (CollUtil.isEmpty(pageResult.getList())) {
  94 + return success(PageResult.empty(pageResult.getTotal()));
  95 + }
  96 + // 2. 拼接数据
  97 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  98 + convertSet(pageResult.getList(), statusType -> Long.parseLong(statusType.getCreator())));
  99 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
  100 + convertSetByFlatMap(pageResult.getList(), CrmBusinessStatusTypeDO::getDeptIds, Collection::stream));
  101 + return success(BeanUtils.toBean(pageResult, CrmBusinessStatusRespVO.class, statusTypeVO -> {
  102 + statusTypeVO.setCreator(userMap.get(NumberUtils.parseLong(statusTypeVO.getCreator())).getNickname());
  103 + statusTypeVO.setDeptNames(convertList(statusTypeVO.getDeptIds(),
  104 + deptId -> deptMap.containsKey(deptId) ? deptMap.get(deptId).getName() : null));
  105 + }));
  106 + }
  107 +
  108 + @GetMapping("/type-simple-list")
  109 + @Operation(summary = "获得商机状态组列表")
  110 + public CommonResult<List<CrmBusinessStatusRespVO>> getBusinessStatusTypeSimpleList() {
  111 + List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.getBusinessStatusTypeList();
  112 + // 过滤掉部门不匹配的
  113 + Long deptId = adminUserApi.getUser(getLoginUserId()).getDeptId();
  114 + list.removeIf(statusType -> CollUtil.isNotEmpty(statusType.getDeptIds()) && !statusType.getDeptIds().contains(deptId));
  115 + return success(BeanUtils.toBean(list, CrmBusinessStatusRespVO.class));
  116 + }
  117 +
  118 + @GetMapping("/status-simple-list")
  119 + @Operation(summary = "获得商机状态列表")
  120 + @Parameter(name = "typeId", description = "商机状态组", required = true, example = "1024")
  121 + public CommonResult<List<CrmBusinessStatusRespVO.Status>> getBusinessStatusSimpleList(@RequestParam("typeId") Long typeId) {
  122 + List<CrmBusinessStatusDO> list = businessStatusTypeService.getBusinessStatusListByTypeId(typeId);
  123 + return success(BeanUtils.toBean(list, CrmBusinessStatusRespVO.Status.class));
  124 + }
  125 +
  126 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.business;  
2 -  
3 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
4 -import cn.iocoder.yudao.framework.common.pojo.CommonResult;  
5 -import cn.iocoder.yudao.framework.common.pojo.PageParam;  
6 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
7 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;  
8 -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;  
9 -import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;  
10 -import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;  
11 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO;  
12 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO;  
13 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO;  
14 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO;  
15 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO;  
16 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO;  
17 -import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusConvert;  
18 -import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusTypeConvert;  
19 -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;  
20 -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;  
21 -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;  
22 -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusTypeService;  
23 -import cn.iocoder.yudao.module.system.api.dept.DeptApi;  
24 -import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;  
25 -import io.swagger.v3.oas.annotations.Operation;  
26 -import io.swagger.v3.oas.annotations.Parameter;  
27 -import io.swagger.v3.oas.annotations.tags.Tag;  
28 -import jakarta.annotation.Resource;  
29 -import jakarta.servlet.http.HttpServletResponse;  
30 -import jakarta.validation.Valid;  
31 -import org.springframework.security.access.prepost.PreAuthorize;  
32 -import org.springframework.validation.annotation.Validated;  
33 -import org.springframework.web.bind.annotation.*;  
34 -  
35 -import java.io.IOException;  
36 -import java.util.Collection;  
37 -import java.util.List;  
38 -import java.util.Set;  
39 -  
40 -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;  
41 -import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;  
42 -  
43 -@Tag(name = "管理后台 - CRM 商机状态类型")  
44 -@RestController  
45 -@RequestMapping("/crm/business-status-type")  
46 -@Validated  
47 -public class CrmBusinessStatusTypeController {  
48 -  
49 - @Resource  
50 - private CrmBusinessStatusTypeService businessStatusTypeService;  
51 -  
52 - @Resource  
53 - private CrmBusinessStatusService businessStatusService;  
54 -  
55 - @Resource  
56 - private DeptApi deptApi;  
57 -  
58 - @PostMapping("/create")  
59 - @Operation(summary = "创建商机状态类型")  
60 - @PreAuthorize("@ss.hasPermission('crm:business-status-type:create')")  
61 - public CommonResult<Long> createBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeSaveReqVO createReqVO) {  
62 - return success(businessStatusTypeService.createBusinessStatusType(createReqVO));  
63 - }  
64 -  
65 - @PutMapping("/update")  
66 - @Operation(summary = "更新商机状态类型")  
67 - @PreAuthorize("@ss.hasPermission('crm:business-status-type:update')")  
68 - public CommonResult<Boolean> updateBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeSaveReqVO updateReqVO) {  
69 - businessStatusTypeService.updateBusinessStatusType(updateReqVO);  
70 - return success(true);  
71 - }  
72 -  
73 - @DeleteMapping("/delete")  
74 - @Operation(summary = "删除商机状态类型")  
75 - @Parameter(name = "id", description = "编号", required = true)  
76 - @PreAuthorize("@ss.hasPermission('crm:business-status-type:delete')")  
77 - public CommonResult<Boolean> deleteBusinessStatusType(@RequestParam("id") Long id) {  
78 - businessStatusTypeService.deleteBusinessStatusType(id);  
79 - return success(true);  
80 - }  
81 -  
82 - @GetMapping("/get")  
83 - @Operation(summary = "获得商机状态类型")  
84 - @Parameter(name = "id", description = "编号", required = true, example = "1024")  
85 - @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")  
86 - public CommonResult<CrmBusinessStatusTypeRespVO> getBusinessStatusType(@RequestParam("id") Long id) {  
87 - CrmBusinessStatusTypeDO statusType = businessStatusTypeService.getBusinessStatusType(id);  
88 - // 处理状态回显  
89 - // TODO @lzxhqs:可以在 businessStatusService 加个 getBusinessStatusListByTypeId 方法,直接返回 List<CrmBusinessStatusDO> 哈,常用的,尽量封装个简单易懂的方法,不用追求绝对通用哈;  
90 - CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();  
91 - queryVO.setTypeId(id);  
92 - List<CrmBusinessStatusDO> statusList = businessStatusService.selectList(queryVO);  
93 - return success(CrmBusinessStatusTypeConvert.INSTANCE.convert(statusType, statusList));  
94 - }  
95 -  
96 - @GetMapping("/page")  
97 - @Operation(summary = "获得商机状态类型分页")  
98 - @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")  
99 - public CommonResult<PageResult<CrmBusinessStatusTypeRespVO>> getBusinessStatusTypePage(@Valid CrmBusinessStatusTypePageReqVO pageReqVO) {  
100 - PageResult<CrmBusinessStatusTypeDO> pageResult = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO);  
101 - // 处理部门回显  
102 - Set<Long> deptIds = CollectionUtils.convertSetByFlatMap(pageResult.getList(), CrmBusinessStatusTypeDO::getDeptIds,Collection::stream);  
103 - List<DeptRespDTO> deptList = deptApi.getDeptList(deptIds);  
104 - return success(CrmBusinessStatusTypeConvert.INSTANCE.convertPage(pageResult, deptList));  
105 - }  
106 -  
107 - @GetMapping("/export-excel")  
108 - @Operation(summary = "导出商机状态类型 Excel")  
109 - @PreAuthorize("@ss.hasPermission('crm:business-status-type:export')")  
110 - @OperateLog(type = EXPORT)  
111 - public void exportBusinessStatusTypeExcel(@Valid CrmBusinessStatusTypePageReqVO pageReqVO,  
112 - HttpServletResponse response) throws IOException {  
113 - pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);  
114 - List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO).getList();  
115 - // 导出 Excel  
116 - ExcelUtils.write(response, "商机状态类型.xls", "数据", CrmBusinessStatusTypeRespVO.class,  
117 - BeanUtils.toBean(list, CrmBusinessStatusTypeRespVO.class));  
118 - }  
119 -  
120 - @GetMapping("/get-simple-list")  
121 - @Operation(summary = "获得商机状态类型列表")  
122 - @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')")  
123 - public CommonResult<List<CrmBusinessStatusTypeRespVO>> getBusinessStatusTypeList() {  
124 - CrmBusinessStatusTypeQueryVO queryVO = new CrmBusinessStatusTypeQueryVO();  
125 - queryVO.setStatus(CommonStatusEnum.ENABLE.getStatus());  
126 - List<CrmBusinessStatusTypeDO> list = businessStatusTypeService.selectList(queryVO);  
127 - return success(BeanUtils.toBean(list, CrmBusinessStatusTypeRespVO.class));  
128 - }  
129 -  
130 - // TODO @ljlleo 这个接口,是不是可以和 getBusinessStatusTypeList 合并成一个?  
131 - @GetMapping("/get-status-list")  
132 - @Operation(summary = "获得商机状态列表")  
133 - @PreAuthorize("@ss.hasPermission('crm:business-status:query')")  
134 - public CommonResult<List<CrmBusinessStatusRespVO>> getBusinessStatusListByTypeId(@RequestParam("typeId") Long typeId) {  
135 - CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO();  
136 - queryVO.setTypeId(typeId);  
137 - List<CrmBusinessStatusDO> list = businessStatusService.selectList(queryVO);  
138 - return success(CrmBusinessStatusConvert.INSTANCE.convertList(list));  
139 - }  
140 -  
141 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessExcelVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;  
2 -  
3 -import com.alibaba.excel.annotation.ExcelProperty;  
4 -import lombok.Data;  
5 -  
6 -import java.math.BigDecimal;  
7 -import java.time.LocalDateTime;  
8 -import java.util.Set;  
9 -  
10 -/**  
11 - * 商机 Excel VO  
12 - *  
13 - * @author ljlleo  
14 - */  
15 -@Data  
16 -public class CrmBusinessExcelVO {  
17 -  
18 - @ExcelProperty("主键")  
19 - private Long id;  
20 -  
21 - @ExcelProperty("商机名称")  
22 - private String name;  
23 -  
24 - @ExcelProperty("商机状态类型编号")  
25 - private Long statusTypeId;  
26 -  
27 - @ExcelProperty("商机状态编号")  
28 - private Long statusId;  
29 -  
30 - @ExcelProperty("下次联系时间")  
31 - private LocalDateTime contactNextTime;  
32 -  
33 - @ExcelProperty("客户编号")  
34 - private Long customerId;  
35 -  
36 - @ExcelProperty("预计成交日期")  
37 - private LocalDateTime dealTime;  
38 -  
39 - @ExcelProperty("商机金额")  
40 - private BigDecimal price;  
41 -  
42 - @ExcelProperty("整单折扣")  
43 - private BigDecimal discountPercent;  
44 -  
45 - @ExcelProperty("产品总金额")  
46 - private BigDecimal productPrice;  
47 -  
48 - @ExcelProperty("备注")  
49 - private String remark;  
50 -  
51 - @ExcelProperty("负责人的用户编号")  
52 - private Long ownerUserId;  
53 -  
54 - @ExcelProperty("创建时间")  
55 - private LocalDateTime createTime;  
56 -  
57 - @ExcelProperty("只读权限的用户编号数组")  
58 - private Set<Long> roUserIds;  
59 -  
60 - @ExcelProperty("读写权限的用户编号数组")  
61 - private Set<Long> rwUserIds;  
62 -  
63 - @ExcelProperty("1赢单2输单3无效")  
64 - private Integer endStatus;  
65 -  
66 - @ExcelProperty("结束时的备注")  
67 - private String endRemark;  
68 -  
69 - @ExcelProperty("最后跟进时间")  
70 - private LocalDateTime contactLastTime;  
71 -  
72 - @ExcelProperty("跟进状态")  
73 - private Integer followUpStatus;  
74 -  
75 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; 1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
2 2
  3 +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
  4 +import com.alibaba.excel.annotation.ExcelProperty;
3 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
4 -import jakarta.validation.constraints.NotNull; 6 +import lombok.AllArgsConstructor;
5 import lombok.Data; 7 import lombok.Data;
6 -import org.springframework.format.annotation.DateTimeFormat; 8 +import lombok.NoArgsConstructor;
7 9
8 import java.math.BigDecimal; 10 import java.math.BigDecimal;
9 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
  12 +import java.util.List;
10 13
11 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;  
12 -  
13 -@Schema(description = "管理后台 - 商机 Response VO") 14 +@Schema(description = "管理后台 - CRM 商机 Response VO")
14 @Data 15 @Data
  16 +@ExcelIgnoreUnannotated
15 public class CrmBusinessRespVO { 17 public class CrmBusinessRespVO {
16 18
17 - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129") 19 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
  20 + @ExcelProperty("编号")
18 private Long id; 21 private Long id;
19 22
20 @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") 23 @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
21 - @NotNull(message = "商机名称不能为空") 24 + @ExcelProperty("商机名称")
22 private String name; 25 private String name;
23 26
24 - @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")  
25 - @NotNull(message = "商机状态类型不能为空") 27 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
  28 + private Long customerId;
  29 + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
  30 + @ExcelProperty("客户名称")
  31 + private String customerName;
  32 +
  33 + @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example ="true")
  34 + @ExcelProperty("跟进状态")
  35 + private Boolean followUpStatus;
  36 +
  37 + @Schema(description = "最后跟进时间")
  38 + @ExcelProperty("最后跟进时间")
  39 + private LocalDateTime contactLastTime;
  40 +
  41 + @Schema(description = "下次联系时间")
  42 + @ExcelProperty("下次联系时间")
  43 + private LocalDateTime contactNextTime;
  44 +
  45 + @Schema(description = "负责人的用户编号", example = "25682")
  46 + @ExcelProperty("负责人的用户编号")
  47 + private Long ownerUserId;
  48 + @Schema(description = "负责人名字", example = "25682")
  49 + @ExcelProperty("负责人名字")
  50 + private String ownerUserName;
  51 + @Schema(description = "负责人部门")
  52 + @ExcelProperty("负责人部门")
  53 + private String ownerUserDeptName;
  54 +
  55 + @Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
26 private Long statusTypeId; 56 private Long statusTypeId;
  57 + @Schema(description = "商机状组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中")
  58 + @ExcelProperty("商机状态组")
  59 + private String statusTypeName;
27 60
28 @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") 61 @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
29 - @NotNull(message = "商机状态不能为空")  
30 private Long statusId; 62 private Long statusId;
  63 + @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中")
  64 + @ExcelProperty("商机状态")
  65 + private String statusName;
31 66
32 - @Schema(description = "下次联系时间")  
33 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
34 - private LocalDateTime contactNextTime; 67 + @Schema
  68 + @ExcelProperty("结束状态")
  69 + private Integer endStatus;
35 70
36 - @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")  
37 - @NotNull(message = "客户不能为空")  
38 - private Long customerId; 71 + @ExcelProperty("结束时的备注")
  72 + private String endRemark;
39 73
40 @Schema(description = "预计成交日期") 74 @Schema(description = "预计成交日期")
41 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) 75 + @ExcelProperty("预计成交日期")
42 private LocalDateTime dealTime; 76 private LocalDateTime dealTime;
43 77
44 - @Schema(description = "商机金额", example = "12371")  
45 - private Integer price; 78 + @Schema(description = "产品总金额", example = "12025")
  79 + @ExcelProperty("产品总金额")
  80 + private BigDecimal totalProductPrice;
46 81
47 - // TODO @ljileo:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题  
48 @Schema(description = "整单折扣") 82 @Schema(description = "整单折扣")
49 - private Integer discountPercent; 83 + @ExcelProperty("整单折扣")
  84 + private BigDecimal discountPercent;
50 85
51 - @Schema(description = "产品总金额", example = "12025")  
52 - private BigDecimal productPrice; 86 + @Schema(description = "商机总金额", example = "12371")
  87 + @ExcelProperty("商机总金额")
  88 + private BigDecimal totalPrice;
53 89
54 @Schema(description = "备注", example = "随便") 90 @Schema(description = "备注", example = "随便")
  91 + @ExcelProperty("备注")
55 private String remark; 92 private String remark;
56 93
  94 + @Schema(description = "创建人", example = "1024")
  95 + @ExcelProperty("创建人")
  96 + private String creator;
  97 + @Schema(description = "创建人名字", example = "芋道源码")
  98 + @ExcelProperty("创建人名字")
  99 + private String creatorName;
  100 +
57 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 101 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  102 + @ExcelProperty("创建时间")
58 private LocalDateTime createTime; 103 private LocalDateTime createTime;
59 104
60 - @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")  
61 - private String customerName; 105 + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  106 + @ExcelProperty("更新时间")
  107 + private LocalDateTime updateTime;
62 108
63 - @Schema(description = "状态类型名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中")  
64 - private String statusTypeName; 109 + @Schema(description = "产品列表")
  110 + private List<Product> products;
65 111
66 - @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中")  
67 - private String statusName; 112 + @Schema(description = "产品列表")
  113 + @Data
  114 + @NoArgsConstructor
  115 + @AllArgsConstructor
  116 + public static class Product {
  117 +
  118 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
  119 + private Long id;
  120 +
  121 + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
  122 + private Long productId;
  123 + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
  124 + private String productName;
  125 + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
  126 + private String productNo;
  127 + @Schema(description = "产品单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
  128 + private Integer productUnit;
  129 +
  130 + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
  131 + private BigDecimal productPrice;
  132 +
  133 + @Schema(description = "商机价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
  134 + private BigDecimal businessPrice;
  135 +
  136 + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
  137 + private BigDecimal count;
  138 +
  139 + @Schema(description = "总计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
  140 + private BigDecimal totalPrice;
  141 +
  142 + }
68 143
69 } 144 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; 1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
2 2
3 -import cn.iocoder.yudao.framework.common.validation.InEnum;  
4 -import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus;  
5 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction; 3 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction;
  4 +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFunction;
6 import com.mzt.logapi.starter.annotation.DiffLogField; 5 import com.mzt.logapi.starter.annotation.DiffLogField;
7 import io.swagger.v3.oas.annotations.media.Schema; 6 import io.swagger.v3.oas.annotations.media.Schema;
8 import jakarta.validation.constraints.NotNull; 7 import jakarta.validation.constraints.NotNull;
@@ -29,75 +28,68 @@ public class CrmBusinessSaveReqVO { @@ -29,75 +28,68 @@ public class CrmBusinessSaveReqVO {
29 @NotNull(message = "商机名称不能为空") 28 @NotNull(message = "商机名称不能为空")
30 private String name; 29 private String name;
31 30
32 - @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")  
33 - @DiffLogField(name = "商机状态")  
34 - @NotNull(message = "商机状态类型不能为空")  
35 - private Long statusTypeId;  
36 -  
37 - @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")  
38 - @DiffLogField(name = "商机状态")  
39 - @NotNull(message = "商机状态不能为空")  
40 - private Long statusId; 31 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")
  32 + @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME)
  33 + @NotNull(message = "客户不能为空")
  34 + private Long customerId;
41 35
42 @Schema(description = "下次联系时间") 36 @Schema(description = "下次联系时间")
43 @DiffLogField(name = "下次联系时间") 37 @DiffLogField(name = "下次联系时间")
44 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) 38 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
45 private LocalDateTime contactNextTime; 39 private LocalDateTime contactNextTime;
46 40
47 - @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299")  
48 - @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME)  
49 - @NotNull(message = "客户不能为空")  
50 - private Long customerId; 41 + @Schema(description = "负责人用户编号", example = "14334")
  42 + @NotNull(message = "负责人不能为空")
  43 + @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME)
  44 + private Long ownerUserId;
  45 +
  46 + @Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714")
  47 + @DiffLogField(name = "商机状态组")
  48 + @NotNull(message = "商机状态组不能为空")
  49 + private Long statusTypeId;
51 50
52 @Schema(description = "预计成交日期") 51 @Schema(description = "预计成交日期")
53 @DiffLogField(name = "预计成交日期") 52 @DiffLogField(name = "预计成交日期")
54 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) 53 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
55 private LocalDateTime dealTime; 54 private LocalDateTime dealTime;
56 55
57 - @Schema(description = "商机金额", example = "12371")  
58 - @DiffLogField(name = "商机金额")  
59 - private Integer price;  
60 -  
61 - @Schema(description = "整单折扣") 56 + @Schema(description = "整单折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "55.00")
62 @DiffLogField(name = "整单折扣") 57 @DiffLogField(name = "整单折扣")
63 - private Integer discountPercent;  
64 -  
65 - @Schema(description = "产品总金额", example = "12025")  
66 - @DiffLogField(name = "产品总金额")  
67 - private BigDecimal productPrice; 58 + @NotNull(message = "整单折扣不能为空")
  59 + private BigDecimal discountPercent;
68 60
69 @Schema(description = "备注", example = "随便") 61 @Schema(description = "备注", example = "随便")
70 @DiffLogField(name = "备注") 62 @DiffLogField(name = "备注")
71 private String remark; 63 private String remark;
72 64
73 - @Schema(description = "结束状态", example = "1")  
74 - @InEnum(CrmBizEndStatus.class)  
75 - private Integer endStatus;  
76 -  
77 @Schema(description = "联系人编号", example = "110") 65 @Schema(description = "联系人编号", example = "110")
78 private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段 66 private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段
79 67
80 - // TODO @puhui999:传递 items 就行啦;  
81 @Schema(description = "产品列表") 68 @Schema(description = "产品列表")
82 - private List<CrmBusinessProductItem> productItems; 69 + private List<Product> products;
83 70
84 @Schema(description = "产品列表") 71 @Schema(description = "产品列表")
85 @Data 72 @Data
86 @NoArgsConstructor 73 @NoArgsConstructor
87 @AllArgsConstructor 74 @AllArgsConstructor
88 - public static class CrmBusinessProductItem { 75 + public static class Product {
89 76
90 @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") 77 @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
91 @NotNull(message = "产品编号不能为空") 78 @NotNull(message = "产品编号不能为空")
92 - private Long id; 79 + private Long productId;
  80 +
  81 + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
  82 + @NotNull(message = "产品单价不能为空")
  83 + private BigDecimal productPrice;
  84 +
  85 + @Schema(description = "商机价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
  86 + @NotNull(message = "商机价格不能为空")
  87 + private BigDecimal businessPrice;
93 88
94 @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") 89 @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
95 @NotNull(message = "产品数量不能为空") 90 @NotNull(message = "产品数量不能为空")
96 private Integer count; 91 private Integer count;
97 92
98 - @Schema(description = "产品折扣")  
99 - private Integer discountPercent;  
100 -  
101 } 93 }
102 94
103 } 95 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessUpdateStatusReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.crm.enums.business.CrmBusinessEndStatusEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.AssertTrue;
  7 +import jakarta.validation.constraints.NotNull;
  8 +import lombok.Data;
  9 +
  10 +@Schema(description = "管理后台 - CRM 商机更新状态 Request VO")
  11 +@Data
  12 +public class CrmBusinessUpdateStatusReqVO {
  13 +
  14 + @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129")
  15 + @NotNull(message = "商机编号不能为空")
  16 + private Long id;
  17 +
  18 + @Schema(description = "状态编号", example = "1")
  19 + private Long statusId;
  20 +
  21 + @Schema(description = "结束状态", example = "1")
  22 + @InEnum(value = CrmBusinessEndStatusEnum.class)
  23 + private Integer endStatus;
  24 +
  25 + @AssertTrue(message = "变更状态不正确")
  26 + public boolean isStatusValid() {
  27 + return statusId != null || endStatus != null;
  28 + }
  29 +
  30 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusPageReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.PageParam;  
4 -import io.swagger.v3.oas.annotations.media.Schema;  
5 -import lombok.Data;  
6 -import lombok.EqualsAndHashCode;  
7 -import lombok.ToString;  
8 -  
9 -@Schema(description = "管理后台 - 商机状态分页 Request VO")  
10 -@Data  
11 -@EqualsAndHashCode(callSuper = true)  
12 -@ToString(callSuper = true)  
13 -public class CrmBusinessStatusPageReqVO extends PageParam {  
14 -  
15 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusQueryVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import lombok.Data;  
5 -import lombok.ToString;  
6 -  
7 -import java.util.Collection;  
8 -  
9 -@Schema(description = "管理后台 - 商机状态 Query VO")  
10 -@Data  
11 -@ToString(callSuper = true)  
12 -public class CrmBusinessStatusQueryVO {  
13 -  
14 - @Schema(description = "主键集合")  
15 - private Collection<Long> idList;  
16 -  
17 - @Schema(description = "状态类型编号")  
18 - private Long typeId;  
19 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status; 1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status;
2 2
3 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
4 -import com.alibaba.excel.annotation.ExcelProperty;  
5 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 4 import lombok.Data;
7 5
  6 +import java.math.BigDecimal;
  7 +import java.time.LocalDateTime;
  8 +import java.util.List;
  9 +
8 @Schema(description = "管理后台 - 商机状态 Response VO") 10 @Schema(description = "管理后台 - 商机状态 Response VO")
9 @Data 11 @Data
10 -@ExcelIgnoreUnannotated  
11 public class CrmBusinessStatusRespVO { 12 public class CrmBusinessStatusRespVO {
12 13
13 - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "23899")  
14 - @ExcelProperty("主键") 14 + @Schema(description = "状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934")
15 private Long id; 15 private Long id;
16 16
17 - @Schema(description = "状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7139")  
18 - @ExcelProperty("状态类型编号")  
19 - private Long typeId;  
20 -  
21 - @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")  
22 - @ExcelProperty("状态名") 17 + @Schema(description = "状态组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
23 private String name; 18 private String name;
24 19
25 - @Schema(description = "赢单率")  
26 - @ExcelProperty("赢单率")  
27 - private String percent; 20 + @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED)
  21 + private List<Long> deptIds;
  22 + @Schema(description = "使用的部门名称", requiredMode = Schema.RequiredMode.REQUIRED)
  23 + private List<String> deptNames;
  24 +
  25 + @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED)
  26 + private String creator;
  27 +
  28 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  29 + private LocalDateTime createTime;
  30 +
  31 + @Schema(description = "状态集合", requiredMode = Schema.RequiredMode.REQUIRED)
  32 + private List<Status> statuses;
  33 +
  34 + @Data
  35 + public static class Status {
  36 +
  37 + @Schema(description = "状态编号", example = "23899")
  38 + private Long id;
  39 +
  40 + @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
  41 + private String name;
  42 +
  43 + @Schema(description = "赢单率", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
  44 + private BigDecimal percent;
  45 +
  46 + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  47 + private Integer sort;
28 48
29 - @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")  
30 - @ExcelProperty("排序")  
31 - private Integer sort; 49 + }
32 50
33 } 51 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status; 1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 -import lombok.Data;  
5 - 4 +import jakarta.validation.Valid;
6 import jakarta.validation.constraints.NotEmpty; 5 import jakarta.validation.constraints.NotEmpty;
7 import jakarta.validation.constraints.NotNull; 6 import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
8 8
9 -@Schema(description = "管理后台 - 商机状态新增/修改 Request VO") 9 +import java.math.BigDecimal;
  10 +import java.util.List;
  11 +
  12 +@Schema(description = "管理后台 - 商机状态组新增/修改 Request VO")
10 @Data 13 @Data
11 public class CrmBusinessStatusSaveReqVO { 14 public class CrmBusinessStatusSaveReqVO {
12 15
13 - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "23899") 16 + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934")
14 private Long id; 17 private Long id;
15 18
16 - @Schema(description = "状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7139")  
17 - @NotNull(message = "状态类型编号不能为空")  
18 - private Long typeId;  
19 -  
20 - @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")  
21 - @NotEmpty(message = "状态名不能为空") 19 + @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
  20 + @NotEmpty(message = "状态类型名不能为空")
22 private String name; 21 private String name;
23 22
24 - // TODO @lzxhqs::percent 应该是 Integer;  
25 - @Schema(description = "赢单率")  
26 - private String percent; 23 + @Schema(description = "使用的部门编号")
  24 + private List<Long> deptIds;
  25 +
  26 + @Schema(description = "商机状态集合", requiredMode = Schema.RequiredMode.REQUIRED)
  27 + @NotEmpty(message = "商机状态集合不能为空")
  28 + @Valid
  29 + private List<Status> statuses;
  30 +
  31 + @Data
  32 + public static class Status {
  33 +
  34 + @Schema(description = "状态编号", example = "23899")
  35 + private Long id;
  36 +
  37 + @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五")
  38 + @NotEmpty(message = "状态名不能为空")
  39 + private String name;
  40 +
  41 + @Schema(description = "赢单率", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
  42 + @NotNull(message = "赢单率不能为空")
  43 + private BigDecimal percent;
  44 +
  45 + @Schema(description = "排序", hidden = true, example = "1")
  46 + private Integer sort;
27 47
28 - // TODO @lzxhqs:这个是不是不用前端新增和修改的时候传递,交给顺序计算出来,存储起来就好了;  
29 - @Schema(description = "排序")  
30 - private Integer sort; 48 + }
31 49
32 } 50 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypePageReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type;  
2 -  
3 -import cn.iocoder.yudao.framework.common.pojo.PageParam;  
4 -import io.swagger.v3.oas.annotations.media.Schema;  
5 -import lombok.Data;  
6 -import lombok.EqualsAndHashCode;  
7 -import lombok.ToString;  
8 -  
9 -@Schema(description = "管理后台 - 商机状态类型分页 Request VO")  
10 -@Data  
11 -@EqualsAndHashCode(callSuper = true)  
12 -@ToString(callSuper = true)  
13 -public class CrmBusinessStatusTypePageReqVO extends PageParam {  
14 -  
15 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeQueryVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import lombok.Data;  
5 -import lombok.ToString;  
6 -  
7 -import java.util.Collection;  
8 -  
9 -@Schema(description = "管理后台 - 商机状态类型 Query VO")  
10 -@Data  
11 -@ToString(callSuper = true)  
12 -public class CrmBusinessStatusTypeQueryVO {  
13 -  
14 - @Schema(description = "主键集合")  
15 - private Collection<Long> idList;  
16 -  
17 - @Schema(description = "状态")  
18 - private Integer status;  
19 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeRespVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type;  
2 -  
3 -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;  
4 -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;  
5 -import com.alibaba.excel.annotation.ExcelProperty;  
6 -import io.swagger.v3.oas.annotations.media.Schema;  
7 -import lombok.Data;  
8 -  
9 -import java.time.LocalDateTime;  
10 -import java.util.List;  
11 -  
12 -@Schema(description = "管理后台 - 商机状态类型 Response VO")  
13 -@Data  
14 -@ExcelIgnoreUnannotated  
15 -public class CrmBusinessStatusTypeRespVO {  
16 -  
17 - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934")  
18 - @ExcelProperty("主键")  
19 - private Long id;  
20 -  
21 - @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")  
22 - @ExcelProperty("状态类型名")  
23 - private String name;  
24 -  
25 - @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED)  
26 - @ExcelProperty("使用的部门编号")  
27 - private List<Long> deptIds;  
28 - @Schema(description = "使用的部门名称", requiredMode = Schema.RequiredMode.REQUIRED)  
29 - @ExcelProperty("使用的部门名称")  
30 - private List<String> deptNames;  
31 -  
32 - @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED)  
33 - @ExcelProperty("创建人")  
34 - private String creator;  
35 -  
36 - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)  
37 - @ExcelProperty("创建时间")  
38 - private LocalDateTime createTime;  
39 -  
40 - // TODO @ljlleo 字段后缀改成 statuses,保持和 deptIds 风格一致;CrmBusinessStatusDO 改成 VO 哈;一般不使用 do 直接返回  
41 - @Schema(description = "状态集合", requiredMode = Schema.RequiredMode.REQUIRED)  
42 - private List<CrmBusinessStatusDO> statusList;  
43 -  
44 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type;  
2 -  
3 -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO;  
4 -import com.google.common.collect.Lists;  
5 -import io.swagger.v3.oas.annotations.media.Schema;  
6 -import lombok.Data;  
7 -  
8 -import jakarta.validation.constraints.NotEmpty;  
9 -import java.util.List;  
10 -  
11 -@Schema(description = "管理后台 - 商机状态类型新增/修改 Request VO")  
12 -@Data  
13 -public class CrmBusinessStatusTypeSaveReqVO {  
14 -  
15 - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934")  
16 - private Long id;  
17 -  
18 - @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")  
19 - @NotEmpty(message = "状态类型名不能为空")  
20 - private String name;  
21 -  
22 - // TODO @lzxhqs: VO 里面,我们不使用默认值哈。这里 Lists.newArrayList() 看看怎么去掉。上面 deptIds 也是类似噢  
23 - @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED)  
24 - private List<Long> deptIds = Lists.newArrayList();  
25 -  
26 - @Schema(description = "商机状态集合", requiredMode = Schema.RequiredMode.REQUIRED)  
27 - private List<CrmBusinessStatusSaveReqVO> statusList;  
28 -  
29 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java
1 package cn.iocoder.yudao.module.crm.controller.admin.clue; 1 package cn.iocoder.yudao.module.crm.controller.admin.clue;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  7 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  10 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
7 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 11 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
8 -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*; 12 +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO;
  13 +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueRespVO;
  14 +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO;
  15 +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO;
9 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; 16 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
  17 +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
10 import cn.iocoder.yudao.module.crm.service.clue.CrmClueService; 18 import cn.iocoder.yudao.module.crm.service.clue.CrmClueService;
  19 +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
  20 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  21 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  22 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  23 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
11 import io.swagger.v3.oas.annotations.Operation; 24 import io.swagger.v3.oas.annotations.Operation;
12 import io.swagger.v3.oas.annotations.Parameter; 25 import io.swagger.v3.oas.annotations.Parameter;
13 import io.swagger.v3.oas.annotations.tags.Tag; 26 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -19,12 +32,18 @@ import org.springframework.validation.annotation.Validated; @@ -19,12 +32,18 @@ import org.springframework.validation.annotation.Validated;
19 import org.springframework.web.bind.annotation.*; 32 import org.springframework.web.bind.annotation.*;
20 33
21 import java.io.IOException; 34 import java.io.IOException;
  35 +import java.util.Collections;
22 import java.util.List; 36 import java.util.List;
  37 +import java.util.Map;
  38 +import java.util.stream.Stream;
23 39
24 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 40 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
25 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; 41 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
  42 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
  43 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
26 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; 44 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
27 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 45 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  46 +import static java.util.Collections.singletonList;
28 47
29 @Tag(name = "管理后台 - 线索") 48 @Tag(name = "管理后台 - 线索")
30 @RestController 49 @RestController
@@ -34,12 +53,19 @@ public class CrmClueController { @@ -34,12 +53,19 @@ public class CrmClueController {
34 53
35 @Resource 54 @Resource
36 private CrmClueService clueService; 55 private CrmClueService clueService;
  56 + @Resource
  57 + private CrmCustomerService customerService;
  58 +
  59 + @Resource
  60 + private AdminUserApi adminUserApi;
  61 + @Resource
  62 + private DeptApi deptApi;
37 63
38 @PostMapping("/create") 64 @PostMapping("/create")
39 @Operation(summary = "创建线索") 65 @Operation(summary = "创建线索")
40 @PreAuthorize("@ss.hasPermission('crm:clue:create')") 66 @PreAuthorize("@ss.hasPermission('crm:clue:create')")
41 public CommonResult<Long> createClue(@Valid @RequestBody CrmClueSaveReqVO createReqVO) { 67 public CommonResult<Long> createClue(@Valid @RequestBody CrmClueSaveReqVO createReqVO) {
42 - return success(clueService.createClue(createReqVO, getLoginUserId())); 68 + return success(clueService.createClue(createReqVO));
43 } 69 }
44 70
45 @PutMapping("/update") 71 @PutMapping("/update")
@@ -65,7 +91,14 @@ public class CrmClueController { @@ -65,7 +91,14 @@ public class CrmClueController {
65 @PreAuthorize("@ss.hasPermission('crm:clue:query')") 91 @PreAuthorize("@ss.hasPermission('crm:clue:query')")
66 public CommonResult<CrmClueRespVO> getClue(@RequestParam("id") Long id) { 92 public CommonResult<CrmClueRespVO> getClue(@RequestParam("id") Long id) {
67 CrmClueDO clue = clueService.getClue(id); 93 CrmClueDO clue = clueService.getClue(id);
68 - return success(BeanUtils.toBean(clue, CrmClueRespVO.class)); 94 + return success(buildClueDetail(clue));
  95 + }
  96 +
  97 + private CrmClueRespVO buildClueDetail(CrmClueDO clue) {
  98 + if (clue == null) {
  99 + return null;
  100 + }
  101 + return buildClueDetailList(singletonList(clue)).get(0);
69 } 102 }
70 103
71 @GetMapping("/page") 104 @GetMapping("/page")
@@ -73,7 +106,7 @@ public class CrmClueController { @@ -73,7 +106,7 @@ public class CrmClueController {
73 @PreAuthorize("@ss.hasPermission('crm:clue:query')") 106 @PreAuthorize("@ss.hasPermission('crm:clue:query')")
74 public CommonResult<PageResult<CrmClueRespVO>> getCluePage(@Valid CrmCluePageReqVO pageVO) { 107 public CommonResult<PageResult<CrmClueRespVO>> getCluePage(@Valid CrmCluePageReqVO pageVO) {
75 PageResult<CrmClueDO> pageResult = clueService.getCluePage(pageVO, getLoginUserId()); 108 PageResult<CrmClueDO> pageResult = clueService.getCluePage(pageVO, getLoginUserId());
76 - return success(BeanUtils.toBean(pageResult, CrmClueRespVO.class)); 109 + return success(new PageResult<>(buildClueDetailList(pageResult.getList()), pageResult.getTotal()));
77 } 110 }
78 111
79 @GetMapping("/export-excel") 112 @GetMapping("/export-excel")
@@ -84,8 +117,33 @@ public class CrmClueController { @@ -84,8 +117,33 @@ public class CrmClueController {
84 pageReqVO.setPageSize(PAGE_SIZE_NONE); 117 pageReqVO.setPageSize(PAGE_SIZE_NONE);
85 List<CrmClueDO> list = clueService.getCluePage(pageReqVO, getLoginUserId()).getList(); 118 List<CrmClueDO> list = clueService.getCluePage(pageReqVO, getLoginUserId()).getList();
86 // 导出 Excel 119 // 导出 Excel
87 - List<CrmClueRespVO> datas = BeanUtils.toBean(list, CrmClueRespVO.class);  
88 - ExcelUtils.write(response, "线索.xls", "数据", CrmClueRespVO.class, datas); 120 + ExcelUtils.write(response, "线索.xls", "数据", CrmClueRespVO.class, buildClueDetailList(list));
  121 + }
  122 +
  123 + private List<CrmClueRespVO> buildClueDetailList(List<CrmClueDO> list) {
  124 + if (CollUtil.isEmpty(list)) {
  125 + return Collections.emptyList();
  126 + }
  127 + // 1.1 获取客户列表
  128 + Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
  129 + convertSet(list, CrmClueDO::getCustomerId));
  130 + // 1.2 获取创建人、负责人列表
  131 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
  132 + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
  133 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  134 + // 2. 转换成 VO
  135 + return BeanUtils.toBean(list, CrmClueRespVO.class, clueVO -> {
  136 + clueVO.setAreaName(AreaUtils.format(clueVO.getAreaId()));
  137 + // 2.1 设置客户名称
  138 + MapUtils.findAndThen(customerMap, clueVO.getCustomerId(), customer -> clueVO.setCustomerName(customer.getName()));
  139 + // 2.2 设置创建人、负责人名称
  140 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(clueVO.getCreator()),
  141 + user -> clueVO.setCreatorName(user.getNickname()));
  142 + MapUtils.findAndThen(userMap, clueVO.getOwnerUserId(), user -> {
  143 + clueVO.setOwnerUserName(user.getNickname());
  144 + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> clueVO.setOwnerUserDeptName(dept.getName()));
  145 + });
  146 + });
89 } 147 }
90 148
91 @PutMapping("/transfer") 149 @PutMapping("/transfer")
@@ -96,12 +154,20 @@ public class CrmClueController { @@ -96,12 +154,20 @@ public class CrmClueController {
96 return success(true); 154 return success(true);
97 } 155 }
98 156
99 - @PostMapping("/transform") 157 + @PutMapping("/transform")
100 @Operation(summary = "线索转化为客户") 158 @Operation(summary = "线索转化为客户")
  159 + @Parameter(name = "id", description = "编号", required = true)
101 @PreAuthorize("@ss.hasPermission('crm:clue:update')") 160 @PreAuthorize("@ss.hasPermission('crm:clue:update')")
102 - public CommonResult<Boolean> translateCustomer(@Valid @RequestBody CrmClueTranslateReqVO reqVO) {  
103 - clueService.translateCustomer(reqVO, getLoginUserId()); 161 + public CommonResult<Boolean> transformClue(@RequestParam("id") Long id) {
  162 + clueService.transformClue(id, getLoginUserId());
104 return success(Boolean.TRUE); 163 return success(Boolean.TRUE);
105 } 164 }
106 165
  166 + @GetMapping("/follow-count")
  167 + @Operation(summary = "获得分配给我的、待跟进的线索数量")
  168 + @PreAuthorize("@ss.hasPermission('crm:clue:query')")
  169 + public CommonResult<Long> getFollowClueCount() {
  170 + return success(clueService.getFollowClueCount(getLoginUserId()));
  171 + }
  172 +
107 } 173 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java
@@ -17,6 +17,9 @@ public class CrmCluePageReqVO extends PageParam { @@ -17,6 +17,9 @@ public class CrmCluePageReqVO extends PageParam {
17 @Schema(description = "线索名称", example = "线索xxx") 17 @Schema(description = "线索名称", example = "线索xxx")
18 private String name; 18 private String name;
19 19
  20 + @Schema(description = "转化状态", example = "2048")
  21 + private Boolean transformStatus;
  22 +
20 @Schema(description = "电话", example = "18000000000") 23 @Schema(description = "电话", example = "18000000000")
21 private String telephone; 24 private String telephone;
22 25
@@ -39,4 +42,7 @@ public class CrmCluePageReqVO extends PageParam { @@ -39,4 +42,7 @@ public class CrmCluePageReqVO extends PageParam {
39 @Schema(description = "客户来源", example = "1") 42 @Schema(description = "客户来源", example = "1")
40 private Integer source; 43 private Integer source;
41 44
  45 + @Schema(description = "跟进状态", example = "true")
  46 + private Boolean followUpStatus;
  47 +
42 } 48 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java
@@ -8,12 +8,9 @@ import com.alibaba.excel.annotation.ExcelProperty; @@ -8,12 +8,9 @@ import com.alibaba.excel.annotation.ExcelProperty;
8 import io.swagger.v3.oas.annotations.media.Schema; 8 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 9 import lombok.Data;
10 import lombok.ToString; 10 import lombok.ToString;
11 -import org.springframework.format.annotation.DateTimeFormat;  
12 11
13 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
14 13
15 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;  
16 -  
17 @Schema(description = "管理后台 - 线索 Response VO") 14 @Schema(description = "管理后台 - 线索 Response VO")
18 @Data 15 @Data
19 @ToString(callSuper = true) 16 @ToString(callSuper = true)
@@ -24,58 +21,76 @@ public class CrmClueRespVO { @@ -24,58 +21,76 @@ public class CrmClueRespVO {
24 @ExcelProperty("编号") 21 @ExcelProperty("编号")
25 private Long id; 22 private Long id;
26 23
27 - @Schema(description = "转化状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")  
28 - @ExcelProperty(value = "转化状态", converter = DictConvert.class)  
29 - @DictFormat(DictTypeConstants.BOOLEAN_STRING)  
30 - private Boolean transformStatus; 24 + @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx")
  25 + @ExcelProperty("线索名称")
  26 + private String name;
31 27
32 @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") 28 @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
33 @ExcelProperty(value = "跟进状态", converter = DictConvert.class) 29 @ExcelProperty(value = "跟进状态", converter = DictConvert.class)
34 @DictFormat(DictTypeConstants.BOOLEAN_STRING) 30 @DictFormat(DictTypeConstants.BOOLEAN_STRING)
35 private Boolean followUpStatus; 31 private Boolean followUpStatus;
36 32
37 - @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx")  
38 - @ExcelProperty("线索名称")  
39 - private String name; 33 + @Schema(description = "最后跟进时间")
  34 + @ExcelProperty("最后跟进时间")
  35 + private LocalDateTime contactLastTime;
40 36
41 - @Schema(description = "客户 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "520")  
42 - // TODO 这里需要导出成客户名称  
43 - @ExcelProperty("客户id")  
44 - private Long customerId; 37 + @Schema(description = "最后跟进内容", example = "吃饭、睡觉、打逗逗")
  38 + @ExcelProperty("最后跟进内容")
  39 + private String contactLastContent;
45 40
46 @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00") 41 @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
47 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
48 @ExcelProperty("下次联系时间") 42 @ExcelProperty("下次联系时间")
49 private LocalDateTime contactNextTime; 43 private LocalDateTime contactNextTime;
50 44
51 - @Schema(description = "电话", example = "18000000000")  
52 - @ExcelProperty("电话")  
53 - private String telephone; 45 + @Schema(description = "负责人编号")
  46 + private Long ownerUserId;
  47 + @Schema(description = "负责人名字", example = "25682")
  48 + @ExcelProperty("负责人名字")
  49 + private String ownerUserName;
  50 + @Schema(description = "负责人部门")
  51 + @ExcelProperty("负责人部门")
  52 + private String ownerUserDeptName;
  53 +
  54 + @Schema(description = "转化状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
  55 + @ExcelProperty(value = "转化状态", converter = DictConvert.class)
  56 + @DictFormat(DictTypeConstants.BOOLEAN_STRING)
  57 + private Boolean transformStatus;
  58 +
  59 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "520")
  60 + private Long customerId;
  61 + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户名称")
  62 + @ExcelProperty("客户名称")
  63 + private String customerName;
54 64
55 @Schema(description = "手机号", example = "18000000000") 65 @Schema(description = "手机号", example = "18000000000")
56 @ExcelProperty("手机号") 66 @ExcelProperty("手机号")
57 private String mobile; 67 private String mobile;
58 68
59 - @Schema(description = "地址", example = "北京市海淀区")  
60 - @ExcelProperty("地址")  
61 - private String address; 69 + @Schema(description = "电话", example = "18000000000")
  70 + @ExcelProperty("电话")
  71 + private String telephone;
62 72
63 - @Schema(description = "负责人编号")  
64 - @ExcelProperty("负责人的用户编号")  
65 - private Long ownerUserId; 73 + @Schema(description = "QQ", example = "25682")
  74 + @ExcelProperty("QQ")
  75 + private String qq;
66 76
67 - @Schema(description = "最后跟进时间")  
68 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
69 - @ExcelProperty("最后跟进时间")  
70 - private LocalDateTime contactLastTime; 77 + @Schema(description = "wechat", example = "25682")
  78 + @ExcelProperty("wechat")
  79 + private String wechat;
71 80
72 - @Schema(description = "备注", example = "随便")  
73 - @ExcelProperty("备注")  
74 - private String remark; 81 + @Schema(description = "email", example = "25682")
  82 + @ExcelProperty("email")
  83 + private String email;
75 84
76 - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)  
77 - @ExcelProperty("创建时间")  
78 - private LocalDateTime createTime; 85 + @Schema(description = "地区编号", example = "1024")
  86 + @ExcelProperty("地区编号")
  87 + private Integer areaId;
  88 + @Schema(description = "地区名称", example = "北京市")
  89 + @ExcelProperty("地区名称")
  90 + private String areaName;
  91 + @Schema(description = "详细地址", example = "北京市成华大道")
  92 + @ExcelProperty("详细地址")
  93 + private String detailAddress;
79 94
80 @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") 95 @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
81 @ExcelProperty(value = "所属行业", converter = DictConvert.class) 96 @ExcelProperty(value = "所属行业", converter = DictConvert.class)
@@ -92,24 +107,23 @@ public class CrmClueRespVO { @@ -92,24 +107,23 @@ public class CrmClueRespVO {
92 @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE) 107 @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE)
93 private Integer source; 108 private Integer source;
94 109
95 - @Schema(description = "网址", example = "25682")  
96 - @ExcelProperty("网址")  
97 - private String website;  
98 -  
99 - @Schema(description = "QQ", example = "25682")  
100 - @ExcelProperty("QQ")  
101 - private String qq; 110 + @Schema(description = "备注", example = "随便")
  111 + @ExcelProperty("备注")
  112 + private String remark;
102 113
103 - @Schema(description = "wechat", example = "25682")  
104 - @ExcelProperty("wechat")  
105 - private String wechat; 114 + @Schema(description = "创建人", example = "1024")
  115 + @ExcelProperty("创建人")
  116 + private String creator;
  117 + @Schema(description = "创建人名字", example = "芋道源码")
  118 + @ExcelProperty("创建人名字")
  119 + private String creatorName;
106 120
107 - @Schema(description = "email", example = "25682")  
108 - @ExcelProperty("email")  
109 - private String email; 121 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  122 + @ExcelProperty("创建时间")
  123 + private LocalDateTime createTime;
110 124
111 - @Schema(description = "客户描述", example = "25682")  
112 - @ExcelProperty("客户描述")  
113 - private String description; 125 + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  126 + @ExcelProperty("更新时间")
  127 + private LocalDateTime updateTime;
114 128
115 } 129 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java
@@ -8,10 +8,12 @@ import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLevelEnum; @@ -8,10 +8,12 @@ import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLevelEnum;
8 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerIndustryParseFunction; 8 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerIndustryParseFunction;
9 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerLevelParseFunction; 9 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerLevelParseFunction;
10 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerSourceParseFunction; 10 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerSourceParseFunction;
  11 +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAreaParseFunction;
11 import com.mzt.logapi.starter.annotation.DiffLogField; 12 import com.mzt.logapi.starter.annotation.DiffLogField;
12 import io.swagger.v3.oas.annotations.media.Schema; 13 import io.swagger.v3.oas.annotations.media.Schema;
13 import jakarta.validation.constraints.Email; 14 import jakarta.validation.constraints.Email;
14 import jakarta.validation.constraints.NotEmpty; 15 import jakarta.validation.constraints.NotEmpty;
  16 +import jakarta.validation.constraints.NotNull;
15 import jakarta.validation.constraints.Size; 17 import jakarta.validation.constraints.Size;
16 import lombok.Data; 18 import lombok.Data;
17 import org.springframework.format.annotation.DateTimeFormat; 19 import org.springframework.format.annotation.DateTimeFormat;
@@ -21,7 +23,7 @@ import java.time.LocalDateTime; @@ -21,7 +23,7 @@ import java.time.LocalDateTime;
21 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; 23 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
22 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY; 24 import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY;
23 25
24 -@Schema(description = "管理后台 - CRM 线索 创建/更新 Request VO") 26 +@Schema(description = "管理后台 - CRM 线索创建/更新 Request VO")
25 @Data 27 @Data
26 public class CrmClueSaveReqVO { 28 public class CrmClueSaveReqVO {
27 29
@@ -33,36 +35,53 @@ public class CrmClueSaveReqVO { @@ -33,36 +35,53 @@ public class CrmClueSaveReqVO {
33 @NotEmpty(message = "线索名称不能为空") 35 @NotEmpty(message = "线索名称不能为空")
34 private String name; 36 private String name;
35 37
  38 + @Schema(description = "最后跟进时间")
  39 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  40 + @DiffLogField(name = "最后跟进时间")
  41 + private LocalDateTime contactLastTime;
  42 +
36 @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00") 43 @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
37 @DiffLogField(name = "下次联系时间") 44 @DiffLogField(name = "下次联系时间")
38 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) 45 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
39 private LocalDateTime contactNextTime; 46 private LocalDateTime contactNextTime;
40 47
41 - @Schema(description = "电话", example = "18000000000")  
42 - @DiffLogField(name = "电话")  
43 - @Telephone  
44 - private String telephone; 48 + @Schema(description = "负责人编号", example = "2048")
  49 + @NotNull(message = "负责人编号不能为空")
  50 + private Long ownerUserId;
45 51
46 @Schema(description = "手机号", example = "18000000000") 52 @Schema(description = "手机号", example = "18000000000")
47 @DiffLogField(name = "手机号") 53 @DiffLogField(name = "手机号")
48 @Mobile 54 @Mobile
49 private String mobile; 55 private String mobile;
50 56
51 - @Schema(description = "地址", example = "北京市海淀区")  
52 - @DiffLogField(name = "地址")  
53 - private String address; 57 + @Schema(description = "电话", example = "18000000000")
  58 + @DiffLogField(name = "电话")
  59 + @Telephone
  60 + private String telephone;
54 61
55 - @Schema(description = "最后跟进时间")  
56 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
57 - @DiffLogField(name = "最后跟进时间")  
58 - private LocalDateTime contactLastTime; 62 + @Schema(description = "QQ", example = "123456789")
  63 + @DiffLogField(name = "QQ")
  64 + @Size(max = 20, message = "QQ长度不能超过 20 个字符")
  65 + private String qq;
59 66
60 - @Schema(description = "负责人编号", example = "2048")  
61 - private Long ownerUserId; 67 + @Schema(description = "微信", example = "123456789")
  68 + @DiffLogField(name = "微信")
  69 + @Size(max = 255, message = "微信长度不能超过 255 个字符")
  70 + private String wechat;
62 71
63 - @Schema(description = "备注", example = "随便")  
64 - @DiffLogField(name = "备注")  
65 - private String remark; 72 + @Schema(description = "邮箱", example = "123456789@qq.com")
  73 + @DiffLogField(name = "邮箱")
  74 + @Email(message = "邮箱格式不正确")
  75 + @Size(max = 255, message = "邮箱长度不能超过 255 个字符")
  76 + private String email;
  77 +
  78 + @Schema(description = "地区编号", example = "20158")
  79 + @DiffLogField(name = "地区编号", function = SysAreaParseFunction.NAME)
  80 + private Integer areaId;
  81 +
  82 + @Schema(description = "详细地址", example = "北京市海淀区")
  83 + @DiffLogField(name = "详细地址")
  84 + private String detailAddress;
66 85
67 @Schema(description = "所属行业", example = "1") 86 @Schema(description = "所属行业", example = "1")
68 @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME) 87 @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME)
@@ -78,28 +97,13 @@ public class CrmClueSaveReqVO { @@ -78,28 +97,13 @@ public class CrmClueSaveReqVO {
78 @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME) 97 @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME)
79 private Integer source; 98 private Integer source;
80 99
81 - @Schema(description = "网址", example = "https://www.baidu.com")  
82 - @DiffLogField(name = "网址")  
83 - private String website;  
84 -  
85 - @Schema(description = "QQ", example = "123456789")  
86 - @DiffLogField(name = "QQ")  
87 - @Size(max = 20, message = "QQ长度不能超过 20 个字符")  
88 - private String qq;  
89 -  
90 - @Schema(description = "微信", example = "123456789")  
91 - @DiffLogField(name = "微信")  
92 - @Size(max = 255, message = "微信长度不能超过 255 个字符")  
93 - private String wechat;  
94 -  
95 - @Schema(description = "邮箱", example = "123456789@qq.com")  
96 - @DiffLogField(name = "邮箱")  
97 - @Email(message = "邮箱格式不正确")  
98 - @Size(max = 255, message = "邮箱长度不能超过 255 个字符")  
99 - private String email;  
100 -  
101 @Schema(description = "客户描述", example = "任意文字") 100 @Schema(description = "客户描述", example = "任意文字")
102 @DiffLogField(name = "客户描述") 101 @DiffLogField(name = "客户描述")
103 @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符") 102 @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符")
104 private String description; 103 private String description;
  104 +
  105 + @Schema(description = "备注", example = "随便")
  106 + @DiffLogField(name = "备注")
  107 + private String remark;
  108 +
105 } 109 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTranslateReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.clue.vo;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import jakarta.validation.constraints.NotEmpty;  
5 -import lombok.Data;  
6 -  
7 -import java.util.Set;  
8 -  
9 -@Schema(description = "管理后台 - 线索转化为客户 Request VO")  
10 -@Data  
11 -public class CrmClueTranslateReqVO {  
12 -  
13 - @Schema(description = "线索编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1024, 1025]")  
14 - @NotEmpty(message = "线索编号不能为空")  
15 - private Set<Long> ids;  
16 -  
17 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java
@@ -2,24 +2,24 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact; @@ -2,24 +2,24 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 -import cn.hutool.core.util.NumberUtil;  
6 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  11 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
11 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 12 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
12 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*; 13 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
13 -import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert;  
14 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; 14 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
15 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 15 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
16 -import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;  
17 import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService; 16 import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
18 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; 17 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
19 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; 18 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
  19 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  20 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
20 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 21 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
21 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 22 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
22 -import com.google.common.collect.Lists;  
23 import io.swagger.v3.oas.annotations.Operation; 23 import io.swagger.v3.oas.annotations.Operation;
24 import io.swagger.v3.oas.annotations.Parameter; 24 import io.swagger.v3.oas.annotations.Parameter;
25 import io.swagger.v3.oas.annotations.tags.Tag; 25 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -37,12 +37,13 @@ import java.util.List; @@ -37,12 +37,13 @@ import java.util.List;
37 import java.util.Map; 37 import java.util.Map;
38 import java.util.stream.Stream; 38 import java.util.stream.Stream;
39 39
40 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;  
41 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 40 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
42 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; 41 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
43 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 42 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  43 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
44 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; 44 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
45 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 45 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  46 +import static java.util.Collections.singletonList;
46 47
47 @Tag(name = "管理后台 - CRM 联系人") 48 @Tag(name = "管理后台 - CRM 联系人")
48 @RestController 49 @RestController
@@ -60,6 +61,8 @@ public class CrmContactController { @@ -60,6 +61,8 @@ public class CrmContactController {
60 61
61 @Resource 62 @Resource
62 private AdminUserApi adminUserApi; 63 private AdminUserApi adminUserApi;
  64 + @Resource
  65 + private DeptApi deptApi;
63 66
64 @PostMapping("/create") 67 @PostMapping("/create")
65 @Operation(summary = "创建联系人") 68 @Operation(summary = "创建联系人")
@@ -92,36 +95,24 @@ public class CrmContactController { @@ -92,36 +95,24 @@ public class CrmContactController {
92 @PreAuthorize("@ss.hasPermission('crm:contact:query')") 95 @PreAuthorize("@ss.hasPermission('crm:contact:query')")
93 public CommonResult<CrmContactRespVO> getContact(@RequestParam("id") Long id) { 96 public CommonResult<CrmContactRespVO> getContact(@RequestParam("id") Long id) {
94 CrmContactDO contact = contactService.getContact(id); 97 CrmContactDO contact = contactService.getContact(id);
  98 + return success(buildContactDetail(contact));
  99 + }
  100 +
  101 + private CrmContactRespVO buildContactDetail(CrmContactDO contact) {
95 if (contact == null) { 102 if (contact == null) {
96 - throw exception(ErrorCodeConstants.CONTACT_NOT_EXISTS); 103 + return null;
97 } 104 }
98 - // 1. 获取用户名  
99 - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(CollUtil.removeNull(Lists.newArrayList(  
100 - NumberUtil.parseLong(contact.getCreator()), contact.getOwnerUserId())));  
101 - // 2. 获取客户信息  
102 - List<CrmCustomerDO> customerList = customerService.getCustomerList(  
103 - Collections.singletonList(contact.getCustomerId()));  
104 - // 3. 直属上级  
105 - List<CrmContactDO> parentContactList = contactService.getContactListByIds(  
106 - Collections.singletonList(contact.getParentId()), getLoginUserId());  
107 - return success(CrmContactConvert.INSTANCE.convert(contact, userMap, customerList, parentContactList));  
108 - }  
109 -  
110 - @GetMapping("/list-by-ids")  
111 - @Operation(summary = "获得联系人列表")  
112 - @Parameter(name = "ids", description = "编号", required = true, example = "[1024]")  
113 - @PreAuthorize("@ss.hasPermission('crm:contact:query')")  
114 - public CommonResult<List<CrmContactRespVO>> getContactListByIds(@RequestParam("ids") List<Long> ids) {  
115 - return success(BeanUtils.toBean(contactService.getContactListByIds(ids, getLoginUserId()), CrmContactRespVO.class)); 105 + return buildContactDetailList(singletonList(contact)).get(0);
116 } 106 }
117 107
118 @GetMapping("/simple-all-list") 108 @GetMapping("/simple-all-list")
119 @Operation(summary = "获得联系人的精简列表") 109 @Operation(summary = "获得联系人的精简列表")
120 @PreAuthorize("@ss.hasPermission('crm:contact:query')") 110 @PreAuthorize("@ss.hasPermission('crm:contact:query')")
121 public CommonResult<List<CrmContactRespVO>> getSimpleContactList() { 111 public CommonResult<List<CrmContactRespVO>> getSimpleContactList() {
122 - List<CrmContactDO> list = contactService.getSimpleContactList(getLoginUserId()); 112 + List<CrmContactDO> list = contactService.getContactList(getLoginUserId());
123 return success(convertList(list, contact -> // 只返回 id、name 字段 113 return success(convertList(list, contact -> // 只返回 id、name 字段
124 - new CrmContactRespVO().setId(contact.getId()).setName(contact.getName()))); 114 + new CrmContactRespVO().setId(contact.getId()).setName(contact.getName())
  115 + .setCustomerId(contact.getCustomerId())));
125 } 116 }
126 117
127 @GetMapping("/page") 118 @GetMapping("/page")
@@ -129,7 +120,7 @@ public class CrmContactController { @@ -129,7 +120,7 @@ public class CrmContactController {
129 @PreAuthorize("@ss.hasPermission('crm:contact:query')") 120 @PreAuthorize("@ss.hasPermission('crm:contact:query')")
130 public CommonResult<PageResult<CrmContactRespVO>> getContactPage(@Valid CrmContactPageReqVO pageVO) { 121 public CommonResult<PageResult<CrmContactRespVO>> getContactPage(@Valid CrmContactPageReqVO pageVO) {
131 PageResult<CrmContactDO> pageResult = contactService.getContactPage(pageVO, getLoginUserId()); 122 PageResult<CrmContactDO> pageResult = contactService.getContactPage(pageVO, getLoginUserId());
132 - return success(buildContactDetailPage(pageResult)); 123 + return success(new PageResult<>(buildContactDetailList(pageResult.getList()), pageResult.getTotal()));
133 } 124 }
134 125
135 @GetMapping("/page-by-customer") 126 @GetMapping("/page-by-customer")
@@ -137,7 +128,15 @@ public class CrmContactController { @@ -137,7 +128,15 @@ public class CrmContactController {
137 public CommonResult<PageResult<CrmContactRespVO>> getContactPageByCustomer(@Valid CrmContactPageReqVO pageVO) { 128 public CommonResult<PageResult<CrmContactRespVO>> getContactPageByCustomer(@Valid CrmContactPageReqVO pageVO) {
138 Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空"); 129 Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
139 PageResult<CrmContactDO> pageResult = contactService.getContactPageByCustomerId(pageVO); 130 PageResult<CrmContactDO> pageResult = contactService.getContactPageByCustomerId(pageVO);
140 - return success(buildContactDetailPage(pageResult)); 131 + return success(new PageResult<>(buildContactDetailList(pageResult.getList()), pageResult.getTotal()));
  132 + }
  133 +
  134 + @GetMapping("/page-by-business")
  135 + @Operation(summary = "获得联系人分页,基于指定商机")
  136 + public CommonResult<PageResult<CrmContactRespVO>> getContactPageByBusiness(@Valid CrmContactPageReqVO pageVO) {
  137 + Assert.notNull(pageVO.getBusinessId(), "商机编号不能为空");
  138 + PageResult<CrmContactDO> pageResult = contactService.getContactPageByBusinessId(pageVO);
  139 + return success(new PageResult<>(buildContactDetailList(pageResult.getList()), pageResult.getTotal()));
141 } 140 }
142 141
143 @GetMapping("/export-excel") 142 @GetMapping("/export-excel")
@@ -147,32 +146,39 @@ public class CrmContactController { @@ -147,32 +146,39 @@ public class CrmContactController {
147 public void exportContactExcel(@Valid CrmContactPageReqVO exportReqVO, 146 public void exportContactExcel(@Valid CrmContactPageReqVO exportReqVO,
148 HttpServletResponse response) throws IOException { 147 HttpServletResponse response) throws IOException {
149 exportReqVO.setPageNo(PAGE_SIZE_NONE); 148 exportReqVO.setPageNo(PAGE_SIZE_NONE);
150 - PageResult<CrmContactDO> pageResult = contactService.getContactPage(exportReqVO, getLoginUserId());  
151 - ExcelUtils.write(response, "联系人.xls", "数据", CrmContactRespVO.class,  
152 - buildContactDetailPage(pageResult).getList());  
153 - }  
154 -  
155 - /**  
156 - * 构建详细的联系人分页结果  
157 - *  
158 - * @param pageResult 简单的联系人分页结果  
159 - * @return 详细的联系人分页结果  
160 - */  
161 - private PageResult<CrmContactRespVO> buildContactDetailPage(PageResult<CrmContactDO> pageResult) {  
162 - List<CrmContactDO> contactList = pageResult.getList(); 149 + List<CrmContactDO> list = contactService.getContactPage(exportReqVO, getLoginUserId()).getList();
  150 + ExcelUtils.write(response, "联系人.xls", "数据", CrmContactRespVO.class, buildContactDetailList(list));
  151 + }
  152 +
  153 + private List<CrmContactRespVO> buildContactDetailList(List<CrmContactDO> contactList) {
163 if (CollUtil.isEmpty(contactList)) { 154 if (CollUtil.isEmpty(contactList)) {
164 - return PageResult.empty(pageResult.getTotal()); 155 + return Collections.emptyList();
165 } 156 }
166 - // 1. 获取客户列表  
167 - List<CrmCustomerDO> crmCustomerDOList = customerService.getCustomerList( 157 + // 1.1 获取客户列表
  158 + Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
168 convertSet(contactList, CrmContactDO::getCustomerId)); 159 convertSet(contactList, CrmContactDO::getCustomerId));
169 - // 2. 获取创建人、负责人列表 160 + // 1.2 获取创建人、负责人列表
170 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList, 161 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList,
171 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); 162 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
172 - // 3. 直属上级  
173 - List<CrmContactDO> parentContactList = contactService.getContactListByIds(  
174 - convertSet(contactList, CrmContactDO::getParentId), getLoginUserId());  
175 - return CrmContactConvert.INSTANCE.convertPage(pageResult, userMap, crmCustomerDOList, parentContactList); 163 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  164 + // 1.3 直属上级 Map
  165 + Map<Long, CrmContactDO> parentContactMap = contactService.getContactMap(
  166 + convertSet(contactList, CrmContactDO::getParentId));
  167 + // 2. 转换成 VO
  168 + return BeanUtils.toBean(contactList, CrmContactRespVO.class, contactVO -> {
  169 + contactVO.setAreaName(AreaUtils.format(contactVO.getAreaId()));
  170 + // 2.1 设置客户名称
  171 + MapUtils.findAndThen(customerMap, contactVO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName()));
  172 + // 2.2 设置创建人、负责人名称
  173 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(contactVO.getCreator()),
  174 + user -> contactVO.setCreatorName(user.getNickname()));
  175 + MapUtils.findAndThen(userMap, contactVO.getOwnerUserId(), user -> {
  176 + contactVO.setOwnerUserName(user.getNickname());
  177 + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> contactVO.setOwnerUserDeptName(dept.getName()));
  178 + });
  179 + // 2.3 设置直属上级名称
  180 + findAndThen(parentContactMap, contactVO.getParentId(), contact -> contactVO.setParentName(contact.getName()));
  181 + });
176 } 182 }
177 183
178 @PutMapping("/transfer") 184 @PutMapping("/transfer")
@@ -183,7 +189,7 @@ public class CrmContactController { @@ -183,7 +189,7 @@ public class CrmContactController {
183 return success(true); 189 return success(true);
184 } 190 }
185 191
186 - // ================== 关联/取关联系人 =================== 192 + // ================== 关联/取关商机 ===================
187 193
188 @PostMapping("/create-business-list") 194 @PostMapping("/create-business-list")
189 @Operation(summary = "创建联系人与商机的关联") 195 @Operation(summary = "创建联系人与商机的关联")
@@ -193,6 +199,15 @@ public class CrmContactController { @@ -193,6 +199,15 @@ public class CrmContactController {
193 return success(true); 199 return success(true);
194 } 200 }
195 201
  202 +
  203 + @PostMapping("/create-business-list2")
  204 + @Operation(summary = "创建联系人与商机的关联")
  205 + @PreAuthorize("@ss.hasPermission('crm:contact:create-business')")
  206 + public CommonResult<Boolean> createContactBusinessList2(@Valid @RequestBody CrmContactBusiness2ReqVO createReqVO) {
  207 + contactBusinessLinkService.createContactBusinessList2(createReqVO);
  208 + return success(true);
  209 + }
  210 +
196 @DeleteMapping("/delete-business-list") 211 @DeleteMapping("/delete-business-list")
197 @Operation(summary = "删除联系人与联系人的关联") 212 @Operation(summary = "删除联系人与联系人的关联")
198 @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')") 213 @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')")
@@ -201,4 +216,12 @@ public class CrmContactController { @@ -201,4 +216,12 @@ public class CrmContactController {
201 return success(true); 216 return success(true);
202 } 217 }
203 218
  219 + @DeleteMapping("/delete-business-list2")
  220 + @Operation(summary = "删除联系人与联系人的关联")
  221 + @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')")
  222 + public CommonResult<Boolean> deleteContactBusinessList(@Valid @RequestBody CrmContactBusiness2ReqVO deleteReqVO) {
  223 + contactBusinessLinkService.deleteContactBusinessList2(deleteReqVO);
  224 + return success(true);
  225 + }
  226 +
204 } 227 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusiness2ReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotEmpty;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import lombok.Data;
  7 +
  8 +import java.util.List;
  9 +
  10 +@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 【商机关联联系人】用于关联,取消关联的操作
  11 +@Data
  12 +public class CrmContactBusiness2ReqVO {
  13 +
  14 + @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638")
  15 + @NotNull(message="商机不能为空")
  16 + private Long businessId;
  17 +
  18 + @Schema(description = "联系人编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878")
  19 + @NotEmpty(message="联系人数组不能为空")
  20 + private List<Long> contactIds;
  21 +
  22 +}
0 \ No newline at end of file 23 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java
@@ -7,7 +7,7 @@ import lombok.Data; @@ -7,7 +7,7 @@ import lombok.Data;
7 7
8 import java.util.List; 8 import java.util.List;
9 9
10 -@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 用于关联,取消关联的操作 10 +@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 【联系人关联商机】用于关联,取消关联的操作
11 @Data 11 @Data
12 public class CrmContactBusinessReqVO { 12 public class CrmContactBusinessReqVO {
13 13
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java
@@ -39,4 +39,7 @@ public class CrmContactPageReqVO extends PageParam { @@ -39,4 +39,7 @@ public class CrmContactPageReqVO extends PageParam {
39 @InEnum(CrmSceneTypeEnum.class) 39 @InEnum(CrmSceneTypeEnum.class)
40 private Integer sceneType; // 场景类型,为 null 时则表示全部 40 private Integer sceneType; // 场景类型,为 null 时则表示全部
41 41
  42 + @Schema(description = "商机编号", example = "10430")
  43 + private Long businessId;
  44 +
42 } 45 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java
@@ -20,29 +20,36 @@ public class CrmContactRespVO { @@ -20,29 +20,36 @@ public class CrmContactRespVO {
20 @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167") 20 @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
21 private Long id; 21 private Long id;
22 22
23 - @Schema(description = "姓名", example = "芋艿")  
24 - @ExcelProperty(value = "姓名", order = 1) 23 + @Schema(description = "联系人姓名", example = "芋艿")
  24 + @ExcelProperty(value = "联系人姓名", order = 1)
25 private String name; 25 private String name;
26 26
27 @Schema(description = "客户编号", example = "10795") 27 @Schema(description = "客户编号", example = "10795")
28 private Long customerId; 28 private Long customerId;
  29 + @ExcelProperty(value = "客户名称", order = 2)
  30 + @Schema(description = "客户名字", example = "test")
  31 + private String customerName;
29 32
30 - @Schema(description = "性别")  
31 - @ExcelProperty(value = "性别", converter = DictConvert.class, order = 3)  
32 - @DictFormat(cn.iocoder.yudao.module.system.enums.DictTypeConstants.USER_SEX)  
33 - private Integer sex; 33 + @Schema(description = "最后跟进时间")
  34 + @ExcelProperty(value = "最后跟进时间", order = 6)
  35 + private LocalDateTime contactLastTime;
34 36
35 - @Schema(description = "职位")  
36 - @ExcelProperty(value = "职位", order = 3)  
37 - private String post; 37 + @Schema(description = "最后跟进内容")
  38 + @ExcelProperty(value = "最后跟进内容", order = 6)
  39 + private String contactLastContent;
38 40
39 - @Schema(description = "是否关键决策人")  
40 - @ExcelProperty(value = "是否关键决策人", converter = DictConvert.class, order = 3)  
41 - @DictFormat(DictTypeConstants.BOOLEAN_STRING)  
42 - private Boolean master; 41 + @Schema(description = "下次联系时间")
  42 + @ExcelProperty(value = "下次联系时间", order = 6)
  43 + private LocalDateTime contactNextTime;
43 44
44 - @Schema(description = "直属上级", example = "23457")  
45 - private Long parentId; 45 + @Schema(description = "负责人编号")
  46 + private Long ownerUserId;
  47 + @Schema(description = "负责人名字", example = "25682")
  48 + @ExcelProperty("负责人名字")
  49 + private String ownerUserName;
  50 + @Schema(description = "负责人部门")
  51 + @ExcelProperty("负责人部门")
  52 + private String ownerUserDeptName;
46 53
47 @Schema(description = "手机号", example = "1387171766") 54 @Schema(description = "手机号", example = "1387171766")
48 @ExcelProperty(value = "手机号", order = 4) 55 @ExcelProperty(value = "手机号", order = 4)
@@ -52,6 +59,10 @@ public class CrmContactRespVO { @@ -52,6 +59,10 @@ public class CrmContactRespVO {
52 @ExcelProperty(value = "电话", order = 4) 59 @ExcelProperty(value = "电话", order = 4)
53 private String telephone; 60 private String telephone;
54 61
  62 + @Schema(description = "电子邮箱", example = "1111@22.com")
  63 + @ExcelProperty(value = "邮箱", order = 4)
  64 + private String email;
  65 +
55 @Schema(description = "QQ", example = "197272662") 66 @Schema(description = "QQ", example = "197272662")
56 @ExcelProperty(value = "QQ", order = 4) 67 @ExcelProperty(value = "QQ", order = 4)
57 private Long qq; 68 private Long qq;
@@ -60,53 +71,52 @@ public class CrmContactRespVO { @@ -60,53 +71,52 @@ public class CrmContactRespVO {
60 @ExcelProperty(value = "微信", order = 4) 71 @ExcelProperty(value = "微信", order = 4)
61 private String wechat; 72 private String wechat;
62 73
63 - @Schema(description = "电子邮箱", example = "1111@22.com")  
64 - @ExcelProperty(value = "邮箱", order = 4)  
65 - private String email;  
66 -  
67 @Schema(description = "地区编号", example = "20158") 74 @Schema(description = "地区编号", example = "20158")
68 private Integer areaId; 75 private Integer areaId;
  76 + @Schema(description = "地区名", example = "上海上海市浦东新区")
  77 + @ExcelProperty(value = "地区", order = 5)
  78 + private String areaName;
69 79
70 @Schema(description = "地址") 80 @Schema(description = "地址")
71 @ExcelProperty(value = "地址", order = 5) 81 @ExcelProperty(value = "地址", order = 5)
72 private String detailAddress; 82 private String detailAddress;
73 83
74 - @Schema(description = "备注", example = "你说的对")  
75 - @ExcelProperty(value = "备注", order = 6)  
76 - private String remark; 84 + @Schema(description = "性别")
  85 + @ExcelProperty(value = "性别", converter = DictConvert.class, order = 3)
  86 + @DictFormat(cn.iocoder.yudao.module.system.enums.DictTypeConstants.USER_SEX)
  87 + private Integer sex;
77 88
78 - @Schema(description = "负责人用户编号", example = "14334")  
79 - private Long ownerUserId; 89 + @Schema(description = "是否关键决策人")
  90 + @ExcelProperty(value = "是否关键决策人", converter = DictConvert.class, order = 3)
  91 + @DictFormat(DictTypeConstants.BOOLEAN_STRING)
  92 + private Boolean master;
80 93
81 - @Schema(description = "最后跟进时间")  
82 - @ExcelProperty(value = "最后跟进时间", order = 6)  
83 - private LocalDateTime contactLastTime; 94 + @Schema(description = "职位")
  95 + @ExcelProperty(value = "职位", order = 3)
  96 + private String post;
84 97
85 - @Schema(description = "下次联系时间")  
86 - @ExcelProperty(value = "下次联系时间", order = 6)  
87 - private LocalDateTime contactNextTime; 98 + @Schema(description = "直属上级", example = "23457")
  99 + private Long parentId;
  100 + @Schema(description = "直属上级名", example = "芋头")
  101 + @ExcelProperty(value = "直属上级", order = 4)
  102 + private String parentName;
  103 +
  104 + @Schema(description = "备注", example = "你说的对")
  105 + @ExcelProperty(value = "备注", order = 6)
  106 + private String remark;
88 107
89 @Schema(description = "创建人", example = "25682") 108 @Schema(description = "创建人", example = "25682")
90 private String creator; 109 private String creator;
91 -  
92 @Schema(description = "创建人名字", example = "test") 110 @Schema(description = "创建人名字", example = "test")
93 @ExcelProperty(value = "创建人", order = 8) 111 @ExcelProperty(value = "创建人", order = 8)
94 private String creatorName; 112 private String creatorName;
95 113
96 - @ExcelProperty(value = "客户名称", order = 2)  
97 - @Schema(description = "客户名字", example = "test")  
98 - private String customerName; 114 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  115 + @ExcelProperty("创建时间")
  116 + private LocalDateTime createTime;
99 117
100 - @Schema(description = "负责人", example = "test")  
101 - @ExcelProperty(value = "负责人", order = 7)  
102 - private String ownerUserName;  
103 -  
104 - @Schema(description = "直属上级名", example = "芋头")  
105 - @ExcelProperty(value = "直属上级", order = 4)  
106 - private String parentName;  
107 -  
108 - @Schema(description = "地区名", example = "上海上海市浦东新区")  
109 - @ExcelProperty(value = "地区", order = 5)  
110 - private String areaName; 118 + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  119 + @ExcelProperty("更新时间")
  120 + private LocalDateTime updateTime;
111 121
112 } 122 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java
@@ -13,7 +13,6 @@ import org.springframework.format.annotation.DateTimeFormat; @@ -13,7 +13,6 @@ import org.springframework.format.annotation.DateTimeFormat;
13 import java.time.LocalDateTime; 13 import java.time.LocalDateTime;
14 14
15 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; 15 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
16 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;  
17 16
18 @Schema(description = "管理后台 - CRM 联系人创建/更新 Request VO") 17 @Schema(description = "管理后台 - CRM 联系人创建/更新 Request VO")
19 @Data 18 @Data
@@ -28,24 +27,19 @@ public class CrmContactSaveReqVO { @@ -28,24 +27,19 @@ public class CrmContactSaveReqVO {
28 private String name; 27 private String name;
29 28
30 @Schema(description = "客户编号", example = "10795") 29 @Schema(description = "客户编号", example = "10795")
  30 + @NotNull(message = "客户编号不能为空")
31 @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) 31 @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME)
32 private Long customerId; 32 private Long customerId;
33 33
34 - @Schema(description = "性别")  
35 - @DiffLogField(name = "性别", function = SysSexParseFunction.NAME)  
36 - private Integer sex;  
37 -  
38 - @Schema(description = "职位")  
39 - @DiffLogField(name = "职位")  
40 - private String post;  
41 -  
42 - @Schema(description = "是否关键决策人")  
43 - @DiffLogField(name = "关键决策人", function = SysBooleanParseFunction.NAME)  
44 - private Boolean master; 34 + @Schema(description = "下次联系时间")
  35 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
  36 + @DiffLogField(name = "下次联系时间")
  37 + private LocalDateTime contactNextTime;
45 38
46 - @Schema(description = "直属上级", example = "23457")  
47 - @DiffLogField(name = "直属上级", function = CrmContactParseFunction.NAME)  
48 - private Long parentId; 39 + @Schema(description = "负责人用户编号", example = "14334")
  40 + @NotNull(message = "负责人不能为空")
  41 + @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME)
  42 + private Long ownerUserId;
49 43
50 @Schema(description = "手机号", example = "1387171766") 44 @Schema(description = "手机号", example = "1387171766")
51 @Mobile 45 @Mobile
@@ -78,24 +72,25 @@ public class CrmContactSaveReqVO { @@ -78,24 +72,25 @@ public class CrmContactSaveReqVO {
78 @DiffLogField(name = "地址") 72 @DiffLogField(name = "地址")
79 private String detailAddress; 73 private String detailAddress;
80 74
81 - @Schema(description = "备注", example = "你说的对")  
82 - @DiffLogField(name = "备注")  
83 - private String remark; 75 + @Schema(description = "性别")
  76 + @DiffLogField(name = "性别", function = SysSexParseFunction.NAME)
  77 + private Integer sex;
84 78
85 - @Schema(description = "负责人用户编号", example = "14334")  
86 - @NotNull(message = "负责人不能为空")  
87 - @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME)  
88 - private Long ownerUserId; 79 + @Schema(description = "是否关键决策人")
  80 + @DiffLogField(name = "关键决策人", function = SysBooleanParseFunction.NAME)
  81 + private Boolean master;
  82 +
  83 + @Schema(description = "职位")
  84 + @DiffLogField(name = "职位")
  85 + private String post;
89 86
90 - @Schema(description = "最后跟进时间")  
91 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
92 - @DiffLogField(name = "最后跟进时间")  
93 - private LocalDateTime contactLastTime; 87 + @Schema(description = "直属上级", example = "23457")
  88 + @DiffLogField(name = "直属上级", function = CrmContactParseFunction.NAME)
  89 + private Long parentId;
94 90
95 - @Schema(description = "下次联系时间")  
96 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)  
97 - @DiffLogField(name = "下次联系时间")  
98 - private LocalDateTime contactNextTime; 91 + @Schema(description = "备注", example = "你说的对")
  92 + @DiffLogField(name = "备注")
  93 + private String remark;
99 94
100 @Schema(description = "关联商机 ID", example = "122233") 95 @Schema(description = "关联商机 ID", example = "122233")
101 private Long businessId; // 注意:该字段用于在【商机】详情界面「新建联系人」时,自动进行关联 96 private Long businessId; // 注意:该字段用于在【商机】详情界面「新建联系人」时,自动进行关联
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractConfigController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  5 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config.CrmContractConfigRespVO;
  6 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config.CrmContractConfigSaveReqVO;
  7 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO;
  8 +import cn.iocoder.yudao.module.crm.service.contract.CrmContractConfigService;
  9 +import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.tags.Tag;
  11 +import jakarta.annotation.Resource;
  12 +import jakarta.validation.Valid;
  13 +import org.springframework.security.access.prepost.PreAuthorize;
  14 +import org.springframework.validation.annotation.Validated;
  15 +import org.springframework.web.bind.annotation.*;
  16 +
  17 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  18 +
  19 +@Tag(name = "管理后台 - CRM 合同配置")
  20 +@RestController
  21 +@RequestMapping("/crm/contract-config")
  22 +@Validated
  23 +public class CrmContractConfigController {
  24 +
  25 + @Resource
  26 + private CrmContractConfigService contractConfigService;
  27 +
  28 + @GetMapping("/get")
  29 + @Operation(summary = "获取合同配置")
  30 + @PreAuthorize("@ss.hasPermission('crm:contract-config:query')")
  31 + public CommonResult<CrmContractConfigRespVO> getCustomerPoolConfig() {
  32 + CrmContractConfigDO config = contractConfigService.getContractConfig();
  33 + return success(BeanUtils.toBean(config, CrmContractConfigRespVO.class));
  34 + }
  35 +
  36 + @PutMapping("/save")
  37 + @Operation(summary = "更新合同配置")
  38 + @PreAuthorize("@ss.hasPermission('crm:contract-config:update')")
  39 + public CommonResult<Boolean> saveCustomerPoolConfig(@Valid @RequestBody CrmContractConfigSaveReqVO updateReqVO) {
  40 + contractConfigService.saveContractConfig(updateReqVO);
  41 + return success(true);
  42 + }
  43 +
  44 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
@@ -3,16 +3,17 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract; @@ -3,16 +3,17 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 9 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 11 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 12 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
11 -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;  
12 -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO;  
13 -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO;  
14 -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;  
15 -import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert; 13 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO;
  14 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
  15 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO;
  16 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO;
16 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; 17 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
17 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; 18 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
18 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 19 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
@@ -24,6 +25,9 @@ import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; @@ -24,6 +25,9 @@ import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
24 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; 25 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
25 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; 26 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
26 import cn.iocoder.yudao.module.crm.service.product.CrmProductService; 27 import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
  28 +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
  29 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  30 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
27 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 31 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
28 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 32 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
29 import io.swagger.v3.oas.annotations.Operation; 33 import io.swagger.v3.oas.annotations.Operation;
@@ -37,6 +41,7 @@ import org.springframework.validation.annotation.Validated; @@ -37,6 +41,7 @@ import org.springframework.validation.annotation.Validated;
37 import org.springframework.web.bind.annotation.*; 41 import org.springframework.web.bind.annotation.*;
38 42
39 import java.io.IOException; 43 import java.io.IOException;
  44 +import java.math.BigDecimal;
40 import java.util.Collections; 45 import java.util.Collections;
41 import java.util.List; 46 import java.util.List;
42 import java.util.Map; 47 import java.util.Map;
@@ -44,6 +49,7 @@ import java.util.stream.Stream; @@ -44,6 +49,7 @@ import java.util.stream.Stream;
44 49
45 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 50 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
46 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 51 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  52 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
47 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; 53 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
48 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 54 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
49 import static java.util.Collections.singletonList; 55 import static java.util.Collections.singletonList;
@@ -65,7 +71,12 @@ public class CrmContractController { @@ -65,7 +71,12 @@ public class CrmContractController {
65 @Resource 71 @Resource
66 private CrmProductService productService; 72 private CrmProductService productService;
67 @Resource 73 @Resource
  74 + private CrmReceivableService receivableService;
  75 +
  76 + @Resource
68 private AdminUserApi adminUserApi; 77 private AdminUserApi adminUserApi;
  78 + @Resource
  79 + private DeptApi deptApi;
69 80
70 @PostMapping("/create") 81 @PostMapping("/create")
71 @Operation(summary = "创建合同") 82 @Operation(summary = "创建合同")
@@ -96,15 +107,24 @@ public class CrmContractController { @@ -96,15 +107,24 @@ public class CrmContractController {
96 @Parameter(name = "id", description = "编号", required = true, example = "1024") 107 @Parameter(name = "id", description = "编号", required = true, example = "1024")
97 @PreAuthorize("@ss.hasPermission('crm:contract:query')") 108 @PreAuthorize("@ss.hasPermission('crm:contract:query')")
98 public CommonResult<CrmContractRespVO> getContract(@RequestParam("id") Long id) { 109 public CommonResult<CrmContractRespVO> getContract(@RequestParam("id") Long id) {
99 - // 1. 查询合同  
100 CrmContractDO contract = contractService.getContract(id); 110 CrmContractDO contract = contractService.getContract(id);
  111 + return success(buildContractDetail(contract));
  112 + }
  113 +
  114 + private CrmContractRespVO buildContractDetail(CrmContractDO contract) {
101 if (contract == null) { 115 if (contract == null) {
102 - return success(null); 116 + return null;
103 } 117 }
104 -  
105 - // 2. 拼接合同信息  
106 - List<CrmContractRespVO> respVOList = buildContractDetailList(singletonList(contract));  
107 - return success(respVOList.get(0)); 118 + CrmContractRespVO contractVO = buildContractDetailList(singletonList(contract)).get(0);
  119 + // 拼接产品项
  120 + List<CrmContractProductDO> businessProducts = contractService.getContractProductListByContractId(contractVO.getId());
  121 + Map<Long, CrmProductDO> productMap = productService.getProductMap(
  122 + convertSet(businessProducts, CrmContractProductDO::getProductId));
  123 + contractVO.setProducts(BeanUtils.toBean(businessProducts, CrmContractRespVO.Product.class, businessProductVO ->
  124 + MapUtils.findAndThen(productMap, businessProductVO.getProductId(),
  125 + product -> businessProductVO.setProductName(product.getName())
  126 + .setProductNo(product.getNo()).setProductUnit(product.getUnit()))));
  127 + return contractVO;
108 } 128 }
109 129
110 @GetMapping("/page") 130 @GetMapping("/page")
@@ -123,6 +143,14 @@ public class CrmContractController { @@ -123,6 +143,14 @@ public class CrmContractController {
123 return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList()))); 143 return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList())));
124 } 144 }
125 145
  146 + @GetMapping("/page-by-business")
  147 + @Operation(summary = "获得合同分页,基于指定商机")
  148 + public CommonResult<PageResult<CrmContractRespVO>> getContractPageByBusiness(@Valid CrmContractPageReqVO pageVO) {
  149 + Assert.notNull(pageVO.getBusinessId(), "商机编号不能为空");
  150 + PageResult<CrmContractDO> pageResult = contractService.getContractPageByBusinessId(pageVO);
  151 + return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList())));
  152 + }
  153 +
126 @GetMapping("/export-excel") 154 @GetMapping("/export-excel")
127 @Operation(summary = "导出合同 Excel") 155 @Operation(summary = "导出合同 Excel")
128 @PreAuthorize("@ss.hasPermission('crm:contract:export')") 156 @PreAuthorize("@ss.hasPermission('crm:contract:export')")
@@ -151,37 +179,78 @@ public class CrmContractController { @@ -151,37 +179,78 @@ public class CrmContractController {
151 return success(true); 179 return success(true);
152 } 180 }
153 181
154 - /**  
155 - * 构建详细的合同结果  
156 - *  
157 - * @param contractList 原始合同信息  
158 - * @return 细的合同结果  
159 - */  
160 private List<CrmContractRespVO> buildContractDetailList(List<CrmContractDO> contractList) { 182 private List<CrmContractRespVO> buildContractDetailList(List<CrmContractDO> contractList) {
161 if (CollUtil.isEmpty(contractList)) { 183 if (CollUtil.isEmpty(contractList)) {
162 return Collections.emptyList(); 184 return Collections.emptyList();
163 } 185 }
164 - // 1. 获取客户列表  
165 - List<CrmCustomerDO> customerList = customerService.getCustomerList( 186 + // 1.1 获取客户列表
  187 + Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
166 convertSet(contractList, CrmContractDO::getCustomerId)); 188 convertSet(contractList, CrmContractDO::getCustomerId));
167 - // 2. 获取创建人、负责人列表 189 + // 1.2 获取创建人、负责人列表
168 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contractList, 190 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contractList,
169 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); 191 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
170 - // 3. 获取联系人  
171 - Map<Long, CrmContactDO> contactMap = convertMap(contactService.getContactListByIds(convertSet(contractList,  
172 - CrmContractDO::getContactId)), CrmContactDO::getId);  
173 - // 4. 获取商机  
174 - Map<Long, CrmBusinessDO> businessMap = convertMap(businessService.getBusinessList(convertSet(contractList,  
175 - CrmContractDO::getBusinessId)), CrmBusinessDO::getId);  
176 - // 5. 获取合同关联的商品  
177 - Map<Long, CrmContractProductDO> contractProductMap = null;  
178 - List<CrmProductDO> productList = null;  
179 - if (contractList.size() == 1) {  
180 - List<CrmContractProductDO> contractProductList = contractService.getContractProductListByContractId(contractList.get(0).getId());  
181 - contractProductMap = convertMap(contractProductList, CrmContractProductDO::getProductId);  
182 - productList = productService.getProductListByIds(convertSet(contractProductList, CrmContractProductDO::getProductId)); 192 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  193 + // 1.3 获取联系人
  194 + Map<Long, CrmContactDO> contactMap = convertMap(contactService.getContactList(convertSet(contractList,
  195 + CrmContractDO::getSignContactId)), CrmContactDO::getId);
  196 + // 1.4 获取商机
  197 + Map<Long, CrmBusinessDO> businessMap = businessService.getBusinessMap(
  198 + convertSet(contractList, CrmContractDO::getBusinessId));
  199 + // 1.5 获得已回款金额
  200 + Map<Long, BigDecimal> receivablePriceMap = receivableService.getReceivablePriceMapByContractId(
  201 + convertSet(contractList, CrmContractDO::getId));
  202 + // 2. 拼接数据
  203 + return BeanUtils.toBean(contractList, CrmContractRespVO.class, contractVO -> {
  204 + // 2.1 设置客户信息
  205 + findAndThen(customerMap, contractVO.getCustomerId(), customer -> contractVO.setCustomerName(customer.getName()));
  206 + // 2.2 设置用户信息
  207 + findAndThen(userMap, Long.parseLong(contractVO.getCreator()), user -> contractVO.setCreatorName(user.getNickname()));
  208 + MapUtils.findAndThen(userMap, contractVO.getOwnerUserId(), user -> {
  209 + contractVO.setOwnerUserName(user.getNickname());
  210 + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> contractVO.setOwnerUserDeptName(dept.getName()));
  211 + });
  212 + findAndThen(userMap, contractVO.getSignUserId(), user -> contractVO.setSignUserName(user.getNickname()));
  213 + // 2.3 设置联系人信息
  214 + findAndThen(contactMap, contractVO.getSignContactId(), contact -> contractVO.setSignContactName(contact.getName()));
  215 + // 2.4 设置商机信息
  216 + findAndThen(businessMap, contractVO.getBusinessId(), business -> contractVO.setBusinessName(business.getName()));
  217 + // 2.5 设置已回款金额
  218 + contractVO.setTotalReceivablePrice(receivablePriceMap.getOrDefault(contractVO.getId(), BigDecimal.ZERO));
  219 + });
  220 + }
  221 +
  222 + @GetMapping("/audit-count")
  223 + @Operation(summary = "获得待审核合同数量")
  224 + @PreAuthorize("@ss.hasPermission('crm:contract:query')")
  225 + public CommonResult<Long> getAuditContractCount() {
  226 + return success(contractService.getAuditContractCount(getLoginUserId()));
  227 + }
  228 +
  229 + @GetMapping("/remind-count")
  230 + @Operation(summary = "获得即将到期(提醒)的合同数量")
  231 + @PreAuthorize("@ss.hasPermission('crm:contract:query')")
  232 + public CommonResult<Long> getRemindContractCount() {
  233 + return success(contractService.getRemindContractCount(getLoginUserId()));
  234 + }
  235 +
  236 + @GetMapping("/simple-list")
  237 + @Operation(summary = "获得合同精简列表", description = "只包含的合同,主要用于前端的下拉选项")
  238 + @Parameter(name = "customerId", description = "客户编号", required = true)
  239 + @PreAuthorize("@ss.hasPermission('crm:contract:query')")
  240 + public CommonResult<List<CrmContractRespVO>> getContractSimpleList(@RequestParam("customerId") Long customerId) {
  241 + CrmContractPageReqVO pageReqVO = new CrmContractPageReqVO().setCustomerId(customerId);
  242 + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); // 不分页
  243 + PageResult<CrmContractDO> pageResult = contractService.getContractPageByCustomerId(pageReqVO);
  244 + if (CollUtil.isEmpty(pageResult.getList())) {
  245 + return success(Collections.emptyList());
183 } 246 }
184 - return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap, contractProductMap, productList); 247 + // 拼接数据
  248 + Map<Long, BigDecimal> receivablePriceMap = receivableService.getReceivablePriceMapByContractId(
  249 + convertSet(pageResult.getList(), CrmContractDO::getId));
  250 + return success(convertList(pageResult.getList(), contract -> new CrmContractRespVO() // 只返回 id、name 等精简字段
  251 + .setId(contract.getId()).setName(contract.getName()).setAuditStatus(contract.getAuditStatus())
  252 + .setTotalPrice(contract.getTotalPrice())
  253 + .setTotalReceivablePrice(receivablePriceMap.getOrDefault(contract.getId(), BigDecimal.ZERO))));
185 } 254 }
186 255
187 } 256 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +@Schema(description = "管理后台 - CRM 合同配置 Response VO")
  7 +@Data
  8 +public class CrmContractConfigRespVO {
  9 +
  10 + @Schema(description = "是否开启提前提醒", example = "true")
  11 + private Boolean notifyEnabled;
  12 +
  13 + @Schema(description = "提前提醒天数", example = "2")
  14 + private Integer notifyDays;
  15 +
  16 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config;
  2 +
  3 +import cn.hutool.core.util.BooleanUtil;
  4 +import com.fasterxml.jackson.annotation.JsonIgnore;
  5 +import com.mzt.logapi.starter.annotation.DiffLogField;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import jakarta.validation.constraints.AssertTrue;
  8 +import lombok.Data;
  9 +
  10 +import java.util.Objects;
  11 +
  12 +@Schema(description = "管理后台 - CRM 合同配置 Request VO")
  13 +@Data
  14 +public class CrmContractConfigSaveReqVO {
  15 +
  16 + @Schema(description = "是否开启提前提醒", example = "true")
  17 + @DiffLogField(name = "是否开启提前提醒")
  18 + private Boolean notifyEnabled;
  19 +
  20 + @Schema(description = "提前提醒天数", example = "2")
  21 + @DiffLogField(name = "提前提醒天数")
  22 + private Integer notifyDays;
  23 +
  24 + @AssertTrue(message = "提前提醒天数不能为空")
  25 + @JsonIgnore
  26 + public boolean isNotifyDaysValid() {
  27 + if (!BooleanUtil.isTrue(getNotifyEnabled())) {
  28 + return true;
  29 + }
  30 + return Objects.nonNull(getNotifyDays());
  31 + }
  32 +
  33 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractPageReqVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageParam; 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 import cn.iocoder.yudao.framework.common.validation.InEnum; 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
2 2
3 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; 3 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 import com.alibaba.excel.annotation.ExcelProperty; 4 import com.alibaba.excel.annotation.ExcelProperty;
@@ -6,13 +6,11 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -6,13 +6,11 @@ import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.AllArgsConstructor; 6 import lombok.AllArgsConstructor;
7 import lombok.Data; 7 import lombok.Data;
8 import lombok.NoArgsConstructor; 8 import lombok.NoArgsConstructor;
9 -import org.springframework.format.annotation.DateTimeFormat;  
10 9
  10 +import java.math.BigDecimal;
11 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
12 import java.util.List; 12 import java.util.List;
13 13
14 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;  
15 -  
16 @Schema(description = "管理后台 - CRM 合同 Response VO") 14 @Schema(description = "管理后台 - CRM 合同 Response VO")
17 @Data 15 @Data
18 @ExcelIgnoreUnannotated 16 @ExcelIgnoreUnannotated
@@ -26,6 +24,10 @@ public class CrmContractRespVO { @@ -26,6 +24,10 @@ public class CrmContractRespVO {
26 @ExcelProperty("合同名称") 24 @ExcelProperty("合同名称")
27 private String name; 25 private String name;
28 26
  27 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101")
  28 + @ExcelProperty("合同编号")
  29 + private String no;
  30 +
29 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336") 31 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336")
30 @ExcelProperty("客户编号") 32 @ExcelProperty("客户编号")
31 private Long customerId; 33 private Long customerId;
@@ -40,72 +42,74 @@ public class CrmContractRespVO { @@ -40,72 +42,74 @@ public class CrmContractRespVO {
40 @ExcelProperty("商机名称") 42 @ExcelProperty("商机名称")
41 private String businessName; 43 private String businessName;
42 44
  45 + @Schema(description = "最后跟进时间")
  46 + @ExcelProperty("最后跟进时间")
  47 + private LocalDateTime contactLastTime;
  48 +
  49 + @Schema(description = "负责人的用户编号", example = "25682")
  50 + @ExcelProperty("负责人的用户编号")
  51 + private Long ownerUserId;
  52 + @Schema(description = "负责人名字", example = "25682")
  53 + @ExcelProperty("负责人名字")
  54 + private String ownerUserName;
  55 + @Schema(description = "负责人部门")
  56 + @ExcelProperty("负责人部门")
  57 + private String ownerUserDeptName;
  58 +
43 @Schema(description = "工作流编号", example = "1043") 59 @Schema(description = "工作流编号", example = "1043")
44 @ExcelProperty("工作流编号") 60 @ExcelProperty("工作流编号")
45 - private Long processInstanceId; 61 + private String processInstanceId;
  62 +
  63 + @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  64 + @ExcelProperty("审批状态")
  65 + private Integer auditStatus;
46 66
47 @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED) 67 @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED)
48 @ExcelProperty("下单日期") 68 @ExcelProperty("下单日期")
49 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
50 private LocalDateTime orderDate; 69 private LocalDateTime orderDate;
51 70
52 - @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144")  
53 - @ExcelProperty("负责人的用户编号")  
54 - private Long ownerUserId;  
55 -  
56 - // TODO @芋艿:未来应该支持自动生成;  
57 - @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101")  
58 - @ExcelProperty("合同编号")  
59 - private String no;  
60 -  
61 @Schema(description = "开始时间") 71 @Schema(description = "开始时间")
62 @ExcelProperty("开始时间") 72 @ExcelProperty("开始时间")
63 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
64 private LocalDateTime startTime; 73 private LocalDateTime startTime;
65 74
66 @Schema(description = "结束时间") 75 @Schema(description = "结束时间")
67 @ExcelProperty("结束时间") 76 @ExcelProperty("结束时间")
68 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
69 private LocalDateTime endTime; 77 private LocalDateTime endTime;
70 78
71 - @Schema(description = "合同金额", example = "5617")  
72 - @ExcelProperty("合同金额")  
73 - private Integer price; 79 + @Schema(description = "产品总金额", example = "19510")
  80 + @ExcelProperty("产品总金额")
  81 + private BigDecimal totalProductPrice;
74 82
75 @Schema(description = "整单折扣") 83 @Schema(description = "整单折扣")
76 @ExcelProperty("整单折扣") 84 @ExcelProperty("整单折扣")
77 - private Integer discountPercent; 85 + private BigDecimal discountPercent;
78 86
79 - @Schema(description = "产品总金额", example = "19510")  
80 - @ExcelProperty("产品总金额")  
81 - private Integer productPrice; 87 + @Schema(description = "合同金额", example = "5617")
  88 + @ExcelProperty("合同金额")
  89 + private BigDecimal totalPrice;
  90 +
  91 + @Schema(description = "已回款金额", example = "5617")
  92 + @ExcelProperty("已回款金额")
  93 + private BigDecimal totalReceivablePrice;
82 94
83 - @Schema(description = "联系人编号", example = "18546")  
84 - @ExcelProperty("联系人编号")  
85 - private Long contactId;  
86 - @Schema(description = "联系人编号", example = "18546")  
87 - @ExcelProperty("联系人编号")  
88 - private String contactName; 95 + @Schema(description = "客户签约人编号", example = "18546")
  96 + private Long signContactId;
  97 + @Schema(description = "客户签约人", example = "小豆")
  98 + @ExcelProperty("客户签约人")
  99 + private String signContactName;
89 100
90 @Schema(description = "公司签约人", example = "14036") 101 @Schema(description = "公司签约人", example = "14036")
91 - @ExcelProperty("公司签约人")  
92 private Long signUserId; 102 private Long signUserId;
93 - @Schema(description = "公司签约人", example = "14036") 103 + @Schema(description = "公司签约人", example = "小明")
94 @ExcelProperty("公司签约人") 104 @ExcelProperty("公司签约人")
95 private String signUserName; 105 private String signUserName;
96 106
97 - @Schema(description = "最后跟进时间")  
98 - @ExcelProperty("最后跟进时间")  
99 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
100 - private LocalDateTime contactLastTime;  
101 -  
102 @Schema(description = "备注", example = "你猜") 107 @Schema(description = "备注", example = "你猜")
103 @ExcelProperty("备注") 108 @ExcelProperty("备注")
104 private String remark; 109 private String remark;
105 110
106 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 111 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
107 @ExcelProperty("创建时间") 112 @ExcelProperty("创建时间")
108 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
109 private LocalDateTime createTime; 113 private LocalDateTime createTime;
110 114
111 @Schema(description = "创建人", example = "25682") 115 @Schema(description = "创建人", example = "25682")
@@ -118,47 +122,40 @@ public class CrmContractRespVO { @@ -118,47 +122,40 @@ public class CrmContractRespVO {
118 122
119 @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) 123 @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
120 @ExcelProperty("更新时间") 124 @ExcelProperty("更新时间")
121 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
122 private LocalDateTime updateTime; 125 private LocalDateTime updateTime;
123 126
124 - @Schema(description = "负责人", example = "test")  
125 - @ExcelProperty("负责人")  
126 - private String ownerUserName;  
127 -  
128 - @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")  
129 - @ExcelProperty("审批状态")  
130 - private Integer auditStatus;  
131 -  
132 @Schema(description = "产品列表") 127 @Schema(description = "产品列表")
133 - private List<CrmContractProductItemRespVO> productItems; 128 + private List<Product> products;
134 129
135 - // TODO @puhui999:可以直接叫 Item  
136 @Schema(description = "产品列表") 130 @Schema(description = "产品列表")
137 @Data 131 @Data
138 @NoArgsConstructor 132 @NoArgsConstructor
139 @AllArgsConstructor 133 @AllArgsConstructor
140 - public static class CrmContractProductItemRespVO { 134 + public static class Product {
141 135
142 - @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") 136 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888")
143 private Long id; 137 private Long id;
144 138
145 - @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是产品")  
146 - private String name;  
147 -  
148 - @Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "N881")  
149 - private String no; 139 + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
  140 + private Long productId;
  141 + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
  142 + private String productName;
  143 + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
  144 + private String productNo;
  145 + @Schema(description = "产品单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
  146 + private Integer productUnit;
150 147
151 - @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")  
152 - private Integer unit; 148 + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
  149 + private BigDecimal productPrice;
153 150
154 - @Schema(description = "价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")  
155 - private Integer price; 151 + @Schema(description = "合同价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
  152 + private BigDecimal contractPrice;
156 153
157 - @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")  
158 - private Integer count; 154 + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
  155 + private BigDecimal count;
159 156
160 - @Schema(description = "产品折扣", example = "99")  
161 - private Integer discountPercent; 157 + @Schema(description = "总计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
  158 + private BigDecimal totalPrice;
162 159
163 } 160 }
164 161
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractSaveReqVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
2 2
3 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmBusinessParseFunction; 3 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmBusinessParseFunction;
4 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmContactParseFunction; 4 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmContactParseFunction;
@@ -12,6 +12,7 @@ import lombok.Data; @@ -12,6 +12,7 @@ import lombok.Data;
12 import lombok.NoArgsConstructor; 12 import lombok.NoArgsConstructor;
13 import org.springframework.format.annotation.DateTimeFormat; 13 import org.springframework.format.annotation.DateTimeFormat;
14 14
  15 +import java.math.BigDecimal;
15 import java.time.LocalDateTime; 16 import java.time.LocalDateTime;
16 import java.util.List; 17 import java.util.List;
17 18
@@ -38,22 +39,16 @@ public class CrmContractSaveReqVO { @@ -38,22 +39,16 @@ public class CrmContractSaveReqVO {
38 @DiffLogField(name = "商机", function = CrmBusinessParseFunction.NAME) 39 @DiffLogField(name = "商机", function = CrmBusinessParseFunction.NAME)
39 private Long businessId; 40 private Long businessId;
40 41
41 - @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED)  
42 - @DiffLogField(name = "下单日期")  
43 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
44 - @NotNull(message = "下单日期不能为空")  
45 - private LocalDateTime orderDate;  
46 -  
47 @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144") 42 @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144")
48 @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) 43 @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME)
49 @NotNull(message = "负责人不能为空") 44 @NotNull(message = "负责人不能为空")
50 private Long ownerUserId; 45 private Long ownerUserId;
51 46
52 - // TODO @芋艿:未来应该支持自动生成;  
53 - @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101")  
54 - @DiffLogField(name = "合同编号")  
55 - @NotNull(message = "合同编号不能为空")  
56 - private String no; 47 + @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED)
  48 + @DiffLogField(name = "下单日期")
  49 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  50 + @NotNull(message = "下单日期不能为空")
  51 + private LocalDateTime orderDate;
57 52
58 @Schema(description = "开始时间") 53 @Schema(description = "开始时间")
59 @DiffLogField(name = "开始时间") 54 @DiffLogField(name = "开始时间")
@@ -65,21 +60,18 @@ public class CrmContractSaveReqVO { @@ -65,21 +60,18 @@ public class CrmContractSaveReqVO {
65 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) 60 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
66 private LocalDateTime endTime; 61 private LocalDateTime endTime;
67 62
68 - @Schema(description = "合同金额", example = "5617")  
69 - @DiffLogField(name = "合同金额")  
70 - private Integer price;  
71 -  
72 - @Schema(description = "整单折扣") 63 + @Schema(description = "整单折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "55.00")
73 @DiffLogField(name = "整单折扣") 64 @DiffLogField(name = "整单折扣")
74 - private Integer discountPercent; 65 + @NotNull(message = "整单折扣不能为空")
  66 + private BigDecimal discountPercent;
75 67
76 - @Schema(description = "产品总金额", example = "19510")  
77 - @DiffLogField(name = "产品总金额")  
78 - private Integer productPrice; 68 + @Schema(description = "合同金额", example = "5617")
  69 + @DiffLogField(name = "合同金额")
  70 + private BigDecimal totalPrice;
79 71
80 - @Schema(description = "联系人编号", example = "18546")  
81 - @DiffLogField(name = "联系人", function = CrmContactParseFunction.NAME)  
82 - private Long contactId; 72 + @Schema(description = "客户签约人编号", example = "18546")
  73 + @DiffLogField(name = "客户签约人", function = CrmContactParseFunction.NAME)
  74 + private Long signContactId;
83 75
84 @Schema(description = "公司签约人", example = "14036") 76 @Schema(description = "公司签约人", example = "14036")
85 @DiffLogField(name = "公司签约人", function = SysAdminUserParseFunction.NAME) 77 @DiffLogField(name = "公司签约人", function = SysAdminUserParseFunction.NAME)
@@ -89,27 +81,31 @@ public class CrmContractSaveReqVO { @@ -89,27 +81,31 @@ public class CrmContractSaveReqVO {
89 @DiffLogField(name = "备注") 81 @DiffLogField(name = "备注")
90 private String remark; 82 private String remark;
91 83
92 -  
93 @Schema(description = "产品列表") 84 @Schema(description = "产品列表")
94 - private List<CrmContractProductItem> productItems; 85 + private List<Product> products;
95 86
96 @Schema(description = "产品列表") 87 @Schema(description = "产品列表")
97 @Data 88 @Data
98 @NoArgsConstructor 89 @NoArgsConstructor
99 @AllArgsConstructor 90 @AllArgsConstructor
100 - public static class CrmContractProductItem { 91 + public static class Product {
101 92
102 - @Schema(description = "产品编号", example = "20529") 93 + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
103 @NotNull(message = "产品编号不能为空") 94 @NotNull(message = "产品编号不能为空")
104 - private Long id; 95 + private Long productId;
  96 +
  97 + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
  98 + @NotNull(message = "产品单价不能为空")
  99 + private BigDecimal productPrice;
  100 +
  101 + @Schema(description = "合同价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00")
  102 + @NotNull(message = "合同价格不能为空")
  103 + private BigDecimal contractPrice;
105 104
106 @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") 105 @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
107 @NotNull(message = "产品数量不能为空") 106 @NotNull(message = "产品数量不能为空")
108 private Integer count; 107 private Integer count;
109 108
110 - @Schema(description = "产品折扣")  
111 - private Integer discountPercent;  
112 -  
113 } 109 }
114 110
115 } 111 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractTransferReqVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractTransferReqVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract;
2 2
3 import cn.iocoder.yudao.framework.common.validation.InEnum; 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 4 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http deleted
1 -### 请求 /transfer  
2 -PUT {{baseUrl}}/crm/customer/transfer  
3 -Content-Type: application/-id: {{adminTenentId}}json  
4 -Authorization: Bearer {{token}}  
5 -tenant  
6 -  
7 -{  
8 - "id": 10,  
9 - "newOwnerUserId": 127  
10 -}  
11 -  
12 -### 自定义日志记录结果  
13 -### 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=客户转移, bizId=10, content=把客户【张三】的负责人从【芋道源码(15612345678)】变更为了【tttt】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/transfer, userIp=127.0.0.1, userAgent=Apache-HttpClient/4.5.14 (Java/17.0.9))  
14 -  
15 -### diff 日志  
16 -### | 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=更新客户, bizId=11, content=更新了客户【所属行业】从【H 住宿和餐饮业】修改为【D 电力、热力、燃气及水生产和供应业】;【客户等级】从【C (非优先客户)】修改为【A (重点客户)】;【客户来源】从【线上咨询】修改为【预约上门】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/update, userIp=0:0:0:0:0:0:0:1, userAgent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36)  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -2,48 +2,55 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer; @@ -2,48 +2,55 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.map.MapUtil; 4 import cn.hutool.core.map.MapUtil;
5 -import cn.hutool.core.util.ObjUtil; 5 +import cn.iocoder.yudao.framework.common.core.KeyValue;
6 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  9 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
8 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; 10 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
  11 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  13 +import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn;
10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 14 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  15 +import cn.iocoder.yudao.framework.ip.core.Area;
  16 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
11 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 17 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
12 -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;  
13 -import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert; 18 +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
14 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 19 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
15 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; 20 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
16 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService; 21 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService;
17 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; 22 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
18 import cn.iocoder.yudao.module.system.api.dept.DeptApi; 23 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
19 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 24 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  25 +import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
20 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 26 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
21 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 27 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
22 import io.swagger.v3.oas.annotations.Operation; 28 import io.swagger.v3.oas.annotations.Operation;
23 import io.swagger.v3.oas.annotations.Parameter; 29 import io.swagger.v3.oas.annotations.Parameter;
  30 +import io.swagger.v3.oas.annotations.Parameters;
24 import io.swagger.v3.oas.annotations.tags.Tag; 31 import io.swagger.v3.oas.annotations.tags.Tag;
25 import jakarta.annotation.Resource; 32 import jakarta.annotation.Resource;
26 import jakarta.servlet.http.HttpServletResponse; 33 import jakarta.servlet.http.HttpServletResponse;
27 import jakarta.validation.Valid; 34 import jakarta.validation.Valid;
28 -import org.mapstruct.ap.internal.util.Collections;  
29 import org.springframework.security.access.prepost.PreAuthorize; 35 import org.springframework.security.access.prepost.PreAuthorize;
30 import org.springframework.validation.annotation.Validated; 36 import org.springframework.validation.annotation.Validated;
31 import org.springframework.web.bind.annotation.*; 37 import org.springframework.web.bind.annotation.*;
32 38
33 import java.io.IOException; 39 import java.io.IOException;
34 import java.time.LocalDateTime; 40 import java.time.LocalDateTime;
  41 +import java.util.ArrayList;
35 import java.util.Arrays; 42 import java.util.Arrays;
36 import java.util.List; 43 import java.util.List;
37 import java.util.Map; 44 import java.util.Map;
38 import java.util.stream.Stream; 45 import java.util.stream.Stream;
39 46
40 -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;  
41 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 47 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
42 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; 48 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
43 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 49 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
44 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; 50 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
45 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 51 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
46 -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED; 52 +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;
  53 +import static java.util.Collections.singletonList;
47 54
48 @Tag(name = "管理后台 - CRM 客户") 55 @Tag(name = "管理后台 - CRM 客户")
49 @RestController 56 @RestController
@@ -55,10 +62,13 @@ public class CrmCustomerController { @@ -55,10 +62,13 @@ public class CrmCustomerController {
55 private CrmCustomerService customerService; 62 private CrmCustomerService customerService;
56 @Resource 63 @Resource
57 private CrmCustomerPoolConfigService customerPoolConfigService; 64 private CrmCustomerPoolConfigService customerPoolConfigService;
  65 +
58 @Resource 66 @Resource
59 private DeptApi deptApi; 67 private DeptApi deptApi;
60 @Resource 68 @Resource
61 private AdminUserApi adminUserApi; 69 private AdminUserApi adminUserApi;
  70 + @Resource
  71 + private DictDataApi dictDataApi;
62 72
63 @PostMapping("/create") 73 @PostMapping("/create")
64 @Operation(summary = "创建客户") 74 @Operation(summary = "创建客户")
@@ -75,6 +85,18 @@ public class CrmCustomerController { @@ -75,6 +85,18 @@ public class CrmCustomerController {
75 return success(true); 85 return success(true);
76 } 86 }
77 87
  88 + @PutMapping("/update-deal-status")
  89 + @Operation(summary = "更新客户的成交状态")
  90 + @Parameters({
  91 + @Parameter(name = "id", description = "客户编号", required = true),
  92 + @Parameter(name = "dealStatus", description = "成交状态", required = true)
  93 + })
  94 + public CommonResult<Boolean> updateCustomerDealStatus(@RequestParam("id") Long id,
  95 + @RequestParam("dealStatus") Boolean dealStatus) {
  96 + customerService.updateCustomerDealStatus(id, dealStatus);
  97 + return success(true);
  98 + }
  99 +
78 @DeleteMapping("/delete") 100 @DeleteMapping("/delete")
79 @Operation(summary = "删除客户") 101 @Operation(summary = "删除客户")
80 @Parameter(name = "id", description = "客户编号", required = true) 102 @Parameter(name = "id", description = "客户编号", required = true)
@@ -91,94 +113,126 @@ public class CrmCustomerController { @@ -91,94 +113,126 @@ public class CrmCustomerController {
91 public CommonResult<CrmCustomerRespVO> getCustomer(@RequestParam("id") Long id) { 113 public CommonResult<CrmCustomerRespVO> getCustomer(@RequestParam("id") Long id) {
92 // 1. 获取客户 114 // 1. 获取客户
93 CrmCustomerDO customer = customerService.getCustomer(id); 115 CrmCustomerDO customer = customerService.getCustomer(id);
  116 + // 2. 拼接数据
  117 + return success(buildCustomerDetail(customer));
  118 + }
  119 +
  120 + public CrmCustomerRespVO buildCustomerDetail(CrmCustomerDO customer) {
94 if (customer == null) { 121 if (customer == null) {
95 - return success(null); 122 + return null;
96 } 123 }
97 - // 2. 拼接数据  
98 - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(  
99 - Collections.asSet(Long.valueOf(customer.getCreator()), customer.getOwnerUserId()));  
100 - Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));  
101 - return success(CrmCustomerConvert.INSTANCE.convert(customer, userMap, deptMap)); 124 + return buildCustomerDetailList(singletonList(customer)).get(0);
102 } 125 }
103 126
104 @GetMapping("/page") 127 @GetMapping("/page")
105 @Operation(summary = "获得客户分页") 128 @Operation(summary = "获得客户分页")
106 @PreAuthorize("@ss.hasPermission('crm:customer:query')") 129 @PreAuthorize("@ss.hasPermission('crm:customer:query')")
107 public CommonResult<PageResult<CrmCustomerRespVO>> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) { 130 public CommonResult<PageResult<CrmCustomerRespVO>> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
  131 + customerService.autoPutCustomerPool();
108 // 1. 查询客户分页 132 // 1. 查询客户分页
109 PageResult<CrmCustomerDO> pageResult = customerService.getCustomerPage(pageVO, getLoginUserId()); 133 PageResult<CrmCustomerDO> pageResult = customerService.getCustomerPage(pageVO, getLoginUserId());
110 if (CollUtil.isEmpty(pageResult.getList())) { 134 if (CollUtil.isEmpty(pageResult.getList())) {
111 return success(PageResult.empty(pageResult.getTotal())); 135 return success(PageResult.empty(pageResult.getTotal()));
112 } 136 }
113 -  
114 // 2. 拼接数据 137 // 2. 拼接数据
115 - Map<Long, Long> poolDayMap = Boolean.TRUE.equals(pageVO.getPool()) ? null :  
116 - getPoolDayMap(pageResult.getList()); // 客户界面,需要查看距离进入公海的时间  
117 - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(  
118 - convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId()))); 138 + return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal()));
  139 + }
  140 +
  141 + public List<CrmCustomerRespVO> buildCustomerDetailList(List<CrmCustomerDO> list) {
  142 + if (CollUtil.isEmpty(list)) {
  143 + return java.util.Collections.emptyList();
  144 + }
  145 + // 1.1 获取创建人、负责人列表
  146 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(list,
  147 + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
119 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); 148 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
120 - return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, userMap, deptMap, poolDayMap)); 149 + // 1.2 获取距离进入公海的时间
  150 + Map<Long, Long> poolDayMap = getPoolDayMap(list);
  151 + // 2. 转换成 VO
  152 + return BeanUtils.toBean(list, CrmCustomerRespVO.class, customerVO -> {
  153 + customerVO.setAreaName(AreaUtils.format(customerVO.getAreaId()));
  154 + // 2.1 设置创建人、负责人名称
  155 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(customerVO.getCreator()),
  156 + user -> customerVO.setCreatorName(user.getNickname()));
  157 + MapUtils.findAndThen(userMap, customerVO.getOwnerUserId(), user -> {
  158 + customerVO.setOwnerUserName(user.getNickname());
  159 + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> customerVO.setOwnerUserDeptName(dept.getName()));
  160 + });
  161 + // 2.2 设置距离进入公海的时间
  162 + if (customerVO.getOwnerUserId() != null) {
  163 + customerVO.setPoolDay(poolDayMap.get(customerVO.getId()));
  164 + }
  165 + });
121 } 166 }
122 167
123 - @GetMapping("/put-in-pool-remind-page") 168 + @GetMapping("/put-pool-remind-page")
124 @Operation(summary = "获得待进入公海客户分页") 169 @Operation(summary = "获得待进入公海客户分页")
125 @PreAuthorize("@ss.hasPermission('crm:customer:query')") 170 @PreAuthorize("@ss.hasPermission('crm:customer:query')")
126 - public CommonResult<PageResult<CrmCustomerRespVO>> getPutInPoolRemindCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {  
127 - // 获取公海配置 TODO @dbh52:合并到 getPutInPoolRemindCustomerPage 会更合适哈;  
128 - CrmCustomerPoolConfigDO poolConfigDO = customerPoolConfigService.getCustomerPoolConfig();  
129 - if (ObjUtil.isNull(poolConfigDO)  
130 - || Boolean.FALSE.equals(poolConfigDO.getEnabled())  
131 - || Boolean.FALSE.equals(poolConfigDO.getNotifyEnabled())  
132 - ) { // TODO @dbh52:这个括号,一般不换行,在 java 这里;  
133 - throw exception(CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED);  
134 - }  
135 - 171 + public CommonResult<PageResult<CrmCustomerRespVO>> getPutPoolRemindCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
136 // 1. 查询客户分页 172 // 1. 查询客户分页
137 - PageResult<CrmCustomerDO> pageResult = customerService.getPutInPoolRemindCustomerPage(pageVO, poolConfigDO, getLoginUserId());  
138 - if (CollUtil.isEmpty(pageResult.getList())) {  
139 - return success(PageResult.empty(pageResult.getTotal()));  
140 - }  
141 - 173 + PageResult<CrmCustomerDO> pageResult = customerService.getPutPoolRemindCustomerPage(pageVO, getLoginUserId());
142 // 2. 拼接数据 174 // 2. 拼接数据
143 - // TODO @芋艿:合并 getCustomerPage 和 getPutInPoolRemindCustomerPage 的后置处理;  
144 - Map<Long, Long> poolDayMap = getPoolDayMap(pageResult.getList()); // 客户界面,需要查看距离进入公海的时间  
145 - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(  
146 - convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId())));  
147 - Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));  
148 - return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, userMap, deptMap, poolDayMap)); 175 + return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal()));
  176 + }
  177 +
  178 + @GetMapping("/put-pool-remind-count")
  179 + @Operation(summary = "获得待进入公海客户数量")
  180 + @PreAuthorize("@ss.hasPermission('crm:customer:query')")
  181 + public CommonResult<Long> getPutPoolRemindCustomerCount() {
  182 + return success(customerService.getPutPoolRemindCustomerCount(getLoginUserId()));
  183 + }
  184 +
  185 + @GetMapping("/today-contact-count")
  186 + @Operation(summary = "获得今日需联系客户数量")
  187 + @PreAuthorize("@ss.hasPermission('crm:customer:query')")
  188 + public CommonResult<Long> getTodayContactCustomerCount() {
  189 + return success(customerService.getTodayContactCustomerCount(getLoginUserId()));
  190 + }
  191 +
  192 + @GetMapping("/follow-count")
  193 + @Operation(summary = "获得分配给我、待跟进的线索数量的客户数量")
  194 + @PreAuthorize("@ss.hasPermission('crm:customer:query')")
  195 + public CommonResult<Long> getFollowCustomerCount() {
  196 + return success(customerService.getFollowCustomerCount(getLoginUserId()));
149 } 197 }
150 198
151 /** 199 /**
152 - * 获取距离进入公海的时间 200 + * 获取距离进入公海的时间 Map
153 * 201 *
154 - * @param customerList 客户列表  
155 - * @return Map<key 客户编号, value 距离进入公海的时间> 202 + * @param list 客户列表
  203 + * @return key 客户编号, value 距离进入公海的时间
156 */ 204 */
157 - private Map<Long, Long> getPoolDayMap(List<CrmCustomerDO> customerList) { 205 + private Map<Long, Long> getPoolDayMap(List<CrmCustomerDO> list) {
158 CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); 206 CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
159 if (poolConfig == null || !poolConfig.getEnabled()) { 207 if (poolConfig == null || !poolConfig.getEnabled()) {
160 return MapUtil.empty(); 208 return MapUtil.empty();
161 } 209 }
162 - return convertMap(customerList, CrmCustomerDO::getId, customer -> {  
163 - // 1.1 未成交放入公海天数  
164 - long dealExpireDay = 0;  
165 - if (!customer.getDealStatus()) {  
166 - dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime()); 210 + list = CollectionUtils.filterList(list, customer -> {
  211 + // 特殊:如果没负责人,则说明已经在公海,不用计算
  212 + if (customer.getOwnerUserId() == null) {
  213 + return false;
167 } 214 }
  215 + // 已成交 or 已锁定,不进入公海
  216 + return !customer.getDealStatus() && !customer.getLockStatus();
  217 + });
  218 + return convertMap(list, CrmCustomerDO::getId, customer -> {
  219 + // 1.1 未成交放入公海天数
  220 + long dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getOwnerTime());
168 // 1.2 未跟进放入公海天数 221 // 1.2 未跟进放入公海天数
169 - LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime());  
170 - long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime);  
171 - if (contactExpireDay < 0) {  
172 - contactExpireDay = 0; 222 + LocalDateTime lastTime = customer.getOwnerTime();
  223 + if (customer.getContactLastTime() != null && customer.getContactLastTime().isAfter(lastTime)) {
  224 + lastTime = customer.getContactLastTime();
173 } 225 }
  226 + long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime);
174 // 2. 返回最小的天数 227 // 2. 返回最小的天数
175 - return Math.min(dealExpireDay, contactExpireDay); 228 + long poolDay = Math.min(dealExpireDay, contactExpireDay);
  229 + return poolDay > 0 ? poolDay : 0;
176 }); 230 });
177 } 231 }
178 232
179 - @GetMapping(value = "/list-all-simple") 233 + @GetMapping(value = "/simple-list")
180 @Operation(summary = "获取客户精简信息列表", description = "只包含有读权限的客户,主要用于前端的下拉选项") 234 @Operation(summary = "获取客户精简信息列表", description = "只包含有读权限的客户,主要用于前端的下拉选项")
181 - public CommonResult<List<CrmCustomerRespVO>> getSimpleDeptList() { 235 + public CommonResult<List<CrmCustomerRespVO>> getCustomerSimpleList() {
182 CrmCustomerPageReqVO reqVO = new CrmCustomerPageReqVO(); 236 CrmCustomerPageReqVO reqVO = new CrmCustomerPageReqVO();
183 reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 237 reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
184 List<CrmCustomerDO> list = customerService.getCustomerPage(reqVO, getLoginUserId()).getList(); 238 List<CrmCustomerDO> list = customerService.getCustomerPage(reqVO, getLoginUserId()).getList();
@@ -186,7 +240,6 @@ public class CrmCustomerController { @@ -186,7 +240,6 @@ public class CrmCustomerController {
186 new CrmCustomerRespVO().setId(customer.getId()).setName(customer.getName()))); 240 new CrmCustomerRespVO().setId(customer.getId()).setName(customer.getName())));
187 } 241 }
188 242
189 - // TODO @puhui999:公海的导出,前端可以接下  
190 @GetMapping("/export-excel") 243 @GetMapping("/export-excel")
191 @Operation(summary = "导出客户 Excel") 244 @Operation(summary = "导出客户 Excel")
192 @PreAuthorize("@ss.hasPermission('crm:customer:export')") 245 @PreAuthorize("@ss.hasPermission('crm:customer:export')")
@@ -197,7 +250,7 @@ public class CrmCustomerController { @@ -197,7 +250,7 @@ public class CrmCustomerController {
197 List<CrmCustomerDO> list = customerService.getCustomerPage(pageVO, getLoginUserId()).getList(); 250 List<CrmCustomerDO> list = customerService.getCustomerPage(pageVO, getLoginUserId()).getList();
198 // 导出 Excel 251 // 导出 Excel
199 ExcelUtils.write(response, "客户.xls", "数据", CrmCustomerRespVO.class, 252 ExcelUtils.write(response, "客户.xls", "数据", CrmCustomerRespVO.class,
200 - BeanUtils.toBean(list, CrmCustomerRespVO.class)); 253 + buildCustomerDetailList(list));
201 } 254 }
202 255
203 @GetMapping("/get-import-template") 256 @GetMapping("/get-import-template")
@@ -205,15 +258,33 @@ public class CrmCustomerController { @@ -205,15 +258,33 @@ public class CrmCustomerController {
205 public void importTemplate(HttpServletResponse response) throws IOException { 258 public void importTemplate(HttpServletResponse response) throws IOException {
206 // 手动创建导出 demo 259 // 手动创建导出 demo
207 List<CrmCustomerImportExcelVO> list = Arrays.asList( 260 List<CrmCustomerImportExcelVO> list = Arrays.asList(
208 - CrmCustomerImportExcelVO.builder().name("芋道").industryId(1).level(1).source(1).mobile("15601691300").telephone("")  
209 - .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("")  
210 - .areaId(null).detailAddress("").build(),  
211 - CrmCustomerImportExcelVO.builder().name("源码").industryId(1).level(1).source(1).mobile("15601691300").telephone("")  
212 - .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("")  
213 - .areaId(null).detailAddress("").build() 261 + CrmCustomerImportExcelVO.builder().name("芋道").industryId(1).level(1).source(1)
  262 + .mobile("15601691300").telephone("").qq("").wechat("").email("yunai@iocoder.cn")
  263 + .areaId(null).detailAddress("").remark("").build(),
  264 + CrmCustomerImportExcelVO.builder().name("源码").industryId(1).level(1).source(1)
  265 + .mobile("15601691300").telephone("").qq("").wechat("").email("yunai@iocoder.cn")
  266 + .areaId(null).detailAddress("").remark("").build()
214 ); 267 );
215 // 输出 268 // 输出
216 - ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list); 269 + ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list, builderSelectMap());
  270 + }
  271 +
  272 + private List<KeyValue<ExcelColumn, List<String>>> builderSelectMap() {
  273 + List<KeyValue<ExcelColumn, List<String>>> selectMap = new ArrayList<>();
  274 + // 获取地区下拉数据
  275 + // TODO @puhui999:嘿嘿,这里改成省份、城市、区域,三个选项,难度大么?
  276 + Area area = AreaUtils.parseArea(Area.ID_CHINA);
  277 + selectMap.add(new KeyValue<>(ExcelColumn.G, AreaUtils.getAreaNodePathList(area.getChildren())));
  278 + // 获取客户所属行业
  279 + List<String> customerIndustries = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_INDUSTRY);
  280 + selectMap.add(new KeyValue<>(ExcelColumn.I, customerIndustries));
  281 + // 获取客户等级
  282 + List<String> customerLevels = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_LEVEL);
  283 + selectMap.add(new KeyValue<>(ExcelColumn.J, customerLevels));
  284 + // 获取客户来源
  285 + List<String> customerSources = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_SOURCE);
  286 + selectMap.add(new KeyValue<>(ExcelColumn.K, customerSources));
  287 + return selectMap;
217 } 288 }
218 289
219 @PostMapping("/import") 290 @PostMapping("/import")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java
@@ -3,10 +3,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer; @@ -3,10 +3,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.pojo.PageResult; 5 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; 8 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO;
7 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigRespVO; 9 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigRespVO;
8 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO; 10 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO;
9 -import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerLimitConfigConvert;  
10 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; 11 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
11 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerLimitConfigService; 12 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerLimitConfigService;
12 import cn.iocoder.yudao.module.system.api.dept.DeptApi; 13 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -71,11 +72,14 @@ public class CrmCustomerLimitConfigController { @@ -71,11 +72,14 @@ public class CrmCustomerLimitConfigController {
71 @Parameter(name = "id", description = "编号", required = true, example = "1024") 72 @Parameter(name = "id", description = "编号", required = true, example = "1024")
72 @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:query')") 73 @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:query')")
73 public CommonResult<CrmCustomerLimitConfigRespVO> getCustomerLimitConfig(@RequestParam("id") Long id) { 74 public CommonResult<CrmCustomerLimitConfigRespVO> getCustomerLimitConfig(@RequestParam("id") Long id) {
74 - CrmCustomerLimitConfigDO customerLimitConfig = customerLimitConfigService.getCustomerLimitConfig(id); 75 + CrmCustomerLimitConfigDO limitConfig = customerLimitConfigService.getCustomerLimitConfig(id);
75 // 拼接数据 76 // 拼接数据
76 - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(customerLimitConfig.getUserIds());  
77 - Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(customerLimitConfig.getDeptIds());  
78 - return success(CrmCustomerLimitConfigConvert.INSTANCE.convert(customerLimitConfig, userMap, deptMap)); 77 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(limitConfig.getUserIds());
  78 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(limitConfig.getDeptIds());
  79 + return success(BeanUtils.toBean(limitConfig, CrmCustomerLimitConfigRespVO.class, configVO -> {
  80 + configVO.setUsers(CollectionUtils.convertList(configVO.getUserIds(), userMap::get));
  81 + configVO.setDepts(CollectionUtils.convertList(configVO.getDeptIds(), deptMap::get));
  82 + }));
79 } 83 }
80 84
81 @GetMapping("/page") 85 @GetMapping("/page")
@@ -91,7 +95,10 @@ public class CrmCustomerLimitConfigController { @@ -91,7 +95,10 @@ public class CrmCustomerLimitConfigController {
91 convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getUserIds, Collection::stream)); 95 convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getUserIds, Collection::stream));
92 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap( 96 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
93 convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getDeptIds, Collection::stream)); 97 convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getDeptIds, Collection::stream));
94 - return success(CrmCustomerLimitConfigConvert.INSTANCE.convertPage(pageResult, userMap, deptMap)); 98 + return success(BeanUtils.toBean(pageResult, CrmCustomerLimitConfigRespVO.class, configVO -> {
  99 + configVO.setUsers(CollectionUtils.convertList(configVO.getUserIds(), userMap::get));
  100 + configVO.setDepts(CollectionUtils.convertList(configVO.getDeptIds(), deptMap::get));
  101 + }));
95 } 102 }
96 103
97 } 104 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerDistributeReqVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import com.alibaba.excel.annotation.ExcelProperty; 6 import com.alibaba.excel.annotation.ExcelProperty;
6 import lombok.AllArgsConstructor; 7 import lombok.AllArgsConstructor;
@@ -24,28 +25,12 @@ public class CrmCustomerImportExcelVO { @@ -24,28 +25,12 @@ public class CrmCustomerImportExcelVO {
24 @ExcelProperty("客户名称") 25 @ExcelProperty("客户名称")
25 private String name; 26 private String name;
26 27
27 - // TODO @puhui999:industryId、level、source 字段,可以研究下怎么搞下拉框  
28 - @ExcelProperty(value = "所属行业", converter = DictConvert.class)  
29 - @DictFormat(CRM_CUSTOMER_INDUSTRY)  
30 - private Integer industryId;  
31 -  
32 - @ExcelProperty(value = "客户等级", converter = DictConvert.class)  
33 - @DictFormat(CRM_CUSTOMER_LEVEL)  
34 - private Integer level;  
35 -  
36 - @ExcelProperty(value = "客户来源", converter = DictConvert.class)  
37 - @DictFormat(CRM_CUSTOMER_SOURCE)  
38 - private Integer source;  
39 -  
40 @ExcelProperty("手机") 28 @ExcelProperty("手机")
41 private String mobile; 29 private String mobile;
42 30
43 @ExcelProperty("电话") 31 @ExcelProperty("电话")
44 private String telephone; 32 private String telephone;
45 33
46 - @ExcelProperty("网址")  
47 - private String website;  
48 -  
49 @ExcelProperty("QQ") 34 @ExcelProperty("QQ")
50 private String qq; 35 private String qq;
51 36
@@ -55,17 +40,25 @@ public class CrmCustomerImportExcelVO { @@ -55,17 +40,25 @@ public class CrmCustomerImportExcelVO {
55 @ExcelProperty("邮箱") 40 @ExcelProperty("邮箱")
56 private String email; 41 private String email;
57 42
58 - @ExcelProperty("客户描述")  
59 - private String description;  
60 -  
61 - @ExcelProperty("备注")  
62 - private String remark;  
63 -  
64 - // TODO @puhui999:需要选择省市区,需要研究下,怎么搞合理点;  
65 - @ExcelProperty("地区编号") 43 + @ExcelProperty(value = "地区", converter = AreaConvert.class)
66 private Integer areaId; 44 private Integer areaId;
67 45
68 @ExcelProperty("详细地址") 46 @ExcelProperty("详细地址")
69 private String detailAddress; 47 private String detailAddress;
70 48
  49 + @ExcelProperty(value = "所属行业", converter = DictConvert.class)
  50 + @DictFormat(CRM_CUSTOMER_INDUSTRY)
  51 + private Integer industryId;
  52 +
  53 + @ExcelProperty(value = "客户等级", converter = DictConvert.class)
  54 + @DictFormat(CRM_CUSTOMER_LEVEL)
  55 + private Integer level;
  56 +
  57 + @ExcelProperty(value = "客户来源", converter = DictConvert.class)
  58 + @DictFormat(CRM_CUSTOMER_SOURCE)
  59 + private Integer source;
  60 +
  61 + @ExcelProperty("备注")
  62 + private String remark;
  63 +
71 } 64 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportReqVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotNull; 4 import jakarta.validation.constraints.NotNull;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportRespVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Builder; 4 import lombok.Builder;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerLockReqVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerPageReqVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageParam; 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 import cn.iocoder.yudao.framework.common.validation.InEnum; 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
@@ -14,6 +14,19 @@ import lombok.ToString; @@ -14,6 +14,19 @@ import lombok.ToString;
14 @ToString(callSuper = true) 14 @ToString(callSuper = true)
15 public class CrmCustomerPageReqVO extends PageParam { 15 public class CrmCustomerPageReqVO extends PageParam {
16 16
  17 + /**
  18 + * 联系状态 - 今日需联系
  19 + */
  20 + public static final int CONTACT_TODAY = 1;
  21 + /**
  22 + * 联系状态 - 已逾期
  23 + */
  24 + public static final int CONTACT_EXPIRED = 2;
  25 + /**
  26 + * 联系状态 - 已联系
  27 + */
  28 + public static final int CONTACT_ALREADY = 3;
  29 +
17 @Schema(description = "客户名称", example = "赵六") 30 @Schema(description = "客户名称", example = "赵六")
18 private String name; 31 private String name;
19 32
@@ -36,4 +49,10 @@ public class CrmCustomerPageReqVO extends PageParam { @@ -36,4 +49,10 @@ public class CrmCustomerPageReqVO extends PageParam {
36 @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") 49 @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
37 private Boolean pool; // null 则表示为不是公海数据 50 private Boolean pool; // null 则表示为不是公海数据
38 51
  52 + @Schema(description = "联系状态", example = "1")
  53 + private Integer contactStatus; // backlog 查询条件
  54 +
  55 + @Schema(description = "跟进状态", example = "true")
  56 + private Boolean followUpStatus;
  57 +
39 } 58 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
@@ -7,12 +7,9 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; @@ -7,12 +7,9 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
7 import com.alibaba.excel.annotation.ExcelProperty; 7 import com.alibaba.excel.annotation.ExcelProperty;
8 import io.swagger.v3.oas.annotations.media.Schema; 8 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 9 import lombok.Data;
10 -import org.springframework.format.annotation.DateTimeFormat;  
11 10
12 import java.time.LocalDateTime; 11 import java.time.LocalDateTime;
13 12
14 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;  
15 -  
16 @Schema(description = "管理后台 - CRM 客户 Response VO") 13 @Schema(description = "管理后台 - CRM 客户 Response VO")
17 @Data 14 @Data
18 @ExcelIgnoreUnannotated 15 @ExcelIgnoreUnannotated
@@ -31,6 +28,28 @@ public class CrmCustomerRespVO { @@ -31,6 +28,28 @@ public class CrmCustomerRespVO {
31 @DictFormat(DictTypeConstants.BOOLEAN_STRING) 28 @DictFormat(DictTypeConstants.BOOLEAN_STRING)
32 private Boolean followUpStatus; 29 private Boolean followUpStatus;
33 30
  31 + @Schema(description = "最后跟进时间")
  32 + @ExcelProperty("最后跟进时间")
  33 + private LocalDateTime contactLastTime;
  34 +
  35 + @Schema(description = "最后跟进内容", example = "吃饭、睡觉、打逗逗")
  36 + @ExcelProperty("最后跟进内容")
  37 + private String contactLastContent;
  38 +
  39 + @Schema(description = "下次联系时间")
  40 + @ExcelProperty("下次联系时间")
  41 + private LocalDateTime contactNextTime;
  42 +
  43 + @Schema(description = "负责人的用户编号", example = "25682")
  44 + @ExcelProperty("负责人的用户编号")
  45 + private Long ownerUserId;
  46 + @Schema(description = "负责人名字", example = "25682")
  47 + @ExcelProperty("负责人名字")
  48 + private String ownerUserName;
  49 + @Schema(description = "负责人部门")
  50 + @ExcelProperty("负责人部门")
  51 + private String ownerUserDeptName;
  52 +
34 @Schema(description = "锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") 53 @Schema(description = "锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
35 @ExcelProperty(value = "锁定状态", converter = DictConvert.class) 54 @ExcelProperty(value = "锁定状态", converter = DictConvert.class)
36 @DictFormat(DictTypeConstants.BOOLEAN_STRING) 55 @DictFormat(DictTypeConstants.BOOLEAN_STRING)
@@ -41,63 +60,26 @@ public class CrmCustomerRespVO { @@ -41,63 +60,26 @@ public class CrmCustomerRespVO {
41 @DictFormat(DictTypeConstants.BOOLEAN_STRING) 60 @DictFormat(DictTypeConstants.BOOLEAN_STRING)
42 private Boolean dealStatus; 61 private Boolean dealStatus;
43 62
44 - @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")  
45 - @ExcelProperty(value = "所属行业", converter = DictConvert.class)  
46 - @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY)  
47 - private Integer industryId;  
48 -  
49 - @Schema(description = "客户等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")  
50 - @ExcelProperty(value = "客户等级", converter = DictConvert.class)  
51 - @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL)  
52 - private Integer level;  
53 -  
54 - @Schema(description = "客户来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")  
55 - @ExcelProperty(value = "客户来源", converter = DictConvert.class)  
56 - @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE)  
57 - private Integer source;  
58 -  
59 - @Schema(description = "负责人的用户编号", example = "25682") 63 + @Schema(description = "手机", example = "25682")
60 @ExcelProperty("手机") 64 @ExcelProperty("手机")
61 private String mobile; 65 private String mobile;
62 66
63 - @Schema(description = "负责人的用户编号", example = "25682") 67 + @Schema(description = "电话", example = "25682")
64 @ExcelProperty("电话") 68 @ExcelProperty("电话")
65 private String telephone; 69 private String telephone;
66 70
67 - @Schema(description = "负责人的用户编号", example = "25682")  
68 - @ExcelProperty("网址")  
69 - private String website;  
70 -  
71 - @Schema(description = "负责人的用户编号", example = "25682") 71 + @Schema(description = "QQ", example = "25682")
72 @ExcelProperty("QQ") 72 @ExcelProperty("QQ")
73 private String qq; 73 private String qq;
74 74
75 - @Schema(description = "负责人的用户编号", example = "25682") 75 + @Schema(description = "wechat", example = "25682")
76 @ExcelProperty("wechat") 76 @ExcelProperty("wechat")
77 private String wechat; 77 private String wechat;
78 78
79 - @Schema(description = "负责人的用户编号", example = "25682") 79 + @Schema(description = "email", example = "25682")
80 @ExcelProperty("email") 80 @ExcelProperty("email")
81 private String email; 81 private String email;
82 82
83 - @Schema(description = "负责人的用户编号", example = "25682")  
84 - @ExcelProperty("客户描述")  
85 - private String description;  
86 -  
87 - @Schema(description = "负责人的用户编号", example = "25682")  
88 - @ExcelProperty("备注")  
89 - private String remark;  
90 -  
91 - @Schema(description = "负责人的用户编号", example = "25682")  
92 - @ExcelProperty("负责人的用户编号")  
93 - private Long ownerUserId;  
94 - @Schema(description = "负责人名字", example = "25682")  
95 - @ExcelProperty("负责人名字")  
96 - private String ownerUserName;  
97 - @Schema(description = "负责人部门")  
98 - @ExcelProperty("负责人部门")  
99 - private String ownerUserDeptName;  
100 -  
101 @Schema(description = "地区编号", example = "1024") 83 @Schema(description = "地区编号", example = "1024")
102 @ExcelProperty("地区编号") 84 @ExcelProperty("地区编号")
103 private Integer areaId; 85 private Integer areaId;
@@ -108,14 +90,24 @@ public class CrmCustomerRespVO { @@ -108,14 +90,24 @@ public class CrmCustomerRespVO {
108 @ExcelProperty("详细地址") 90 @ExcelProperty("详细地址")
109 private String detailAddress; 91 private String detailAddress;
110 92
111 - @Schema(description = "最后跟进时间")  
112 - @ExcelProperty("最后跟进时间")  
113 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
114 - private LocalDateTime contactLastTime; 93 + @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
  94 + @ExcelProperty(value = "所属行业", converter = DictConvert.class)
  95 + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY)
  96 + private Integer industryId;
115 97
116 - @Schema(description = "下次联系时间")  
117 - @ExcelProperty("下次联系时间")  
118 - private LocalDateTime contactNextTime; 98 + @Schema(description = "客户等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
  99 + @ExcelProperty(value = "客户等级", converter = DictConvert.class)
  100 + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_LEVEL)
  101 + private Integer level;
  102 +
  103 + @Schema(description = "客户来源", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
  104 + @ExcelProperty(value = "客户来源", converter = DictConvert.class)
  105 + @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE)
  106 + private Integer source;
  107 +
  108 + @Schema(description = "负责人的用户编号", example = "25682")
  109 + @ExcelProperty("备注")
  110 + private String remark;
119 111
120 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 112 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
121 @ExcelProperty("创建时间") 113 @ExcelProperty("创建时间")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerSaveReqVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2
3 import cn.iocoder.yudao.framework.common.validation.InEnum; 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 import cn.iocoder.yudao.framework.common.validation.Mobile; 4 import cn.iocoder.yudao.framework.common.validation.Mobile;
@@ -13,6 +13,7 @@ import com.mzt.logapi.starter.annotation.DiffLogField; @@ -13,6 +13,7 @@ import com.mzt.logapi.starter.annotation.DiffLogField;
13 import io.swagger.v3.oas.annotations.media.Schema; 13 import io.swagger.v3.oas.annotations.media.Schema;
14 import jakarta.validation.constraints.Email; 14 import jakarta.validation.constraints.Email;
15 import jakarta.validation.constraints.NotEmpty; 15 import jakarta.validation.constraints.NotEmpty;
  16 +import jakarta.validation.constraints.NotNull;
16 import jakarta.validation.constraints.Size; 17 import jakarta.validation.constraints.Size;
17 import lombok.Data; 18 import lombok.Data;
18 import org.springframework.format.annotation.DateTimeFormat; 19 import org.springframework.format.annotation.DateTimeFormat;
@@ -34,19 +35,14 @@ public class CrmCustomerSaveReqVO { @@ -34,19 +35,14 @@ public class CrmCustomerSaveReqVO {
34 @NotEmpty(message = "客户名称不能为空") 35 @NotEmpty(message = "客户名称不能为空")
35 private String name; 36 private String name;
36 37
37 - @Schema(description = "所属行业", example = "1")  
38 - @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME)  
39 - @DictFormat(CRM_CUSTOMER_INDUSTRY)  
40 - private Integer industryId;  
41 -  
42 - @Schema(description = "客户等级", example = "2")  
43 - @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME)  
44 - @InEnum(CrmCustomerLevelEnum.class)  
45 - private Integer level; 38 + @Schema(description = "下次联系时间")
  39 + @DiffLogField(name = "下次联系时间")
  40 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  41 + private LocalDateTime contactNextTime;
46 42
47 - @Schema(description = "客户来源", example = "3")  
48 - @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME)  
49 - private Integer source; 43 + @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
  44 + @NotNull(message = "负责人的用户编号不能为空")
  45 + private Long ownerUserId;
50 46
51 @Schema(description = "手机", example = "18000000000") 47 @Schema(description = "手机", example = "18000000000")
52 @DiffLogField(name = "手机") 48 @DiffLogField(name = "手机")
@@ -58,10 +54,6 @@ public class CrmCustomerSaveReqVO { @@ -58,10 +54,6 @@ public class CrmCustomerSaveReqVO {
58 @Telephone 54 @Telephone
59 private String telephone; 55 private String telephone;
60 56
61 - @Schema(description = "网址", example = "https://www.baidu.com")  
62 - @DiffLogField(name = "网址")  
63 - private String website;  
64 -  
65 @Schema(description = "QQ", example = "123456789") 57 @Schema(description = "QQ", example = "123456789")
66 @DiffLogField(name = "QQ") 58 @DiffLogField(name = "QQ")
67 @Size(max = 20, message = "QQ长度不能超过 20 个字符") 59 @Size(max = 20, message = "QQ长度不能超过 20 个字符")
@@ -78,15 +70,6 @@ public class CrmCustomerSaveReqVO { @@ -78,15 +70,6 @@ public class CrmCustomerSaveReqVO {
78 @Size(max = 255, message = "邮箱长度不能超过 255 个字符") 70 @Size(max = 255, message = "邮箱长度不能超过 255 个字符")
79 private String email; 71 private String email;
80 72
81 - @Schema(description = "客户描述", example = "任意文字")  
82 - @DiffLogField(name = "客户描述")  
83 - @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符")  
84 - private String description;  
85 -  
86 - @Schema(description = "备注", example = "随便")  
87 - @DiffLogField(name = "备注")  
88 - private String remark;  
89 -  
90 @Schema(description = "地区编号", example = "20158") 73 @Schema(description = "地区编号", example = "20158")
91 @DiffLogField(name = "地区编号", function = SysAreaParseFunction.NAME) 74 @DiffLogField(name = "地区编号", function = SysAreaParseFunction.NAME)
92 private Integer areaId; 75 private Integer areaId;
@@ -95,12 +78,22 @@ public class CrmCustomerSaveReqVO { @@ -95,12 +78,22 @@ public class CrmCustomerSaveReqVO {
95 @DiffLogField(name = "详细地址") 78 @DiffLogField(name = "详细地址")
96 private String detailAddress; 79 private String detailAddress;
97 80
98 - @Schema(description = "下次联系时间")  
99 - @DiffLogField(name = "下次联系时间")  
100 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
101 - private LocalDateTime contactNextTime; 81 + @Schema(description = "所属行业", example = "1")
  82 + @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME)
  83 + @DictFormat(CRM_CUSTOMER_INDUSTRY)
  84 + private Integer industryId;
102 85
103 - @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")  
104 - private Long ownerUserId; 86 + @Schema(description = "客户等级", example = "2")
  87 + @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME)
  88 + @InEnum(CrmCustomerLevelEnum.class)
  89 + private Integer level;
  90 +
  91 + @Schema(description = "客户来源", example = "3")
  92 + @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME)
  93 + private Integer source;
  94 +
  95 + @Schema(description = "备注", example = "随便")
  96 + @DiffLogField(name = "备注")
  97 + private String remark;
105 98
106 } 99 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerTransferReqVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2
3 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 3 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
4 import io.swagger.v3.oas.annotations.media.Schema; 4 import io.swagger.v3.oas.annotations.media.Schema;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.collection.MapUtils; 5 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
7 import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO; 8 import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO;
8 import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordRespVO; 9 import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordRespVO;
9 import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO; 10 import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO;
@@ -13,6 +14,8 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; @@ -13,6 +14,8 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO;
13 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; 14 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
14 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; 15 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
15 import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService; 16 import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService;
  17 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  18 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
16 import io.swagger.v3.oas.annotations.Operation; 19 import io.swagger.v3.oas.annotations.Operation;
17 import io.swagger.v3.oas.annotations.Parameter; 20 import io.swagger.v3.oas.annotations.Parameter;
18 import io.swagger.v3.oas.annotations.tags.Tag; 21 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -26,7 +29,7 @@ import java.util.ArrayList; @@ -26,7 +29,7 @@ import java.util.ArrayList;
26 import java.util.Map; 29 import java.util.Map;
27 30
28 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 31 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
29 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; 32 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
30 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; 33 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
31 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 34 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
32 35
@@ -44,6 +47,9 @@ public class CrmFollowUpRecordController { @@ -44,6 +47,9 @@ public class CrmFollowUpRecordController {
44 @Resource 47 @Resource
45 private CrmBusinessService businessService; 48 private CrmBusinessService businessService;
46 49
  50 + @Resource
  51 + private AdminUserApi adminUserApi;
  52 +
47 @PostMapping("/create") 53 @PostMapping("/create")
48 @Operation(summary = "创建跟进记录") 54 @Operation(summary = "创建跟进记录")
49 @PreAuthorize("@ss.hasPermission('crm:follow-up-record:create')") 55 @PreAuthorize("@ss.hasPermission('crm:follow-up-record:create')")
@@ -74,17 +80,24 @@ public class CrmFollowUpRecordController { @@ -74,17 +80,24 @@ public class CrmFollowUpRecordController {
74 @PreAuthorize("@ss.hasPermission('crm:follow-up-record:query')") 80 @PreAuthorize("@ss.hasPermission('crm:follow-up-record:query')")
75 public CommonResult<PageResult<CrmFollowUpRecordRespVO>> getFollowUpRecordPage(@Valid CrmFollowUpRecordPageReqVO pageReqVO) { 81 public CommonResult<PageResult<CrmFollowUpRecordRespVO>> getFollowUpRecordPage(@Valid CrmFollowUpRecordPageReqVO pageReqVO) {
76 PageResult<CrmFollowUpRecordDO> pageResult = followUpRecordService.getFollowUpRecordPage(pageReqVO); 82 PageResult<CrmFollowUpRecordDO> pageResult = followUpRecordService.getFollowUpRecordPage(pageReqVO);
77 - /// 拼接数据  
78 - Map<Long, CrmContactDO> contactMap = convertMap(contactService.getContactListByIds(  
79 - convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream())), CrmContactDO::getId);  
80 - Map<Long, CrmBusinessDO> businessMap = convertMap(businessService.getBusinessList(  
81 - convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream())), CrmBusinessDO::getId); 83 + // 1.1 查询联系人和商机
  84 + Map<Long, CrmContactDO> contactMap = contactService.getContactMap(
  85 + convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream()));
  86 + Map<Long, CrmBusinessDO> businessMap = businessService.getBusinessMap(
  87 + convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream()));
  88 + // 1.2 查询用户
  89 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  90 + convertSet(pageResult.getList(), item -> Long.valueOf(item.getCreator())));
  91 + // 2. 拼接数据
82 PageResult<CrmFollowUpRecordRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmFollowUpRecordRespVO.class, record -> { 92 PageResult<CrmFollowUpRecordRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmFollowUpRecordRespVO.class, record -> {
83 - record.setContactNames(new ArrayList<>()).setBusinessNames(new ArrayList<>());  
84 - record.getContactIds().forEach(id -> MapUtils.findAndThen(contactMap, id,  
85 - contact -> record.getContactNames().add(contact.getName())));  
86 - record.getContactIds().forEach(id -> MapUtils.findAndThen(businessMap, id,  
87 - business -> record.getBusinessNames().add(business.getName()))); 93 + // 2.1 设置联系人和商机信息
  94 + record.setBusinesses(new ArrayList<>()).setContacts(new ArrayList<>());
  95 + record.getContactIds().forEach(id -> MapUtils.findAndThen(contactMap, id, contact ->
  96 + record.getContacts().add(new CrmBusinessRespVO().setId(contact.getId()).setName(contact.getName()))));
  97 + record.getContactIds().forEach(id -> MapUtils.findAndThen(businessMap, id, business ->
  98 + record.getBusinesses().add(new CrmBusinessRespVO().setId(business.getId()).setName(business.getName()))));
  99 + // 2.2 设置用户信息
  100 + MapUtils.findAndThen(userMap, Long.valueOf(record.getCreator()), user -> record.setCreatorName(user.getNickname()));
88 }); 101 });
89 return success(voPageResult); 102 return success(voPageResult);
90 } 103 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.followup.vo; 1 package cn.iocoder.yudao.module.crm.controller.admin.followup.vo;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
4 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; 5 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
  6 +import com.alibaba.excel.annotation.ExcelProperty;
5 import io.swagger.v3.oas.annotations.media.Schema; 7 import io.swagger.v3.oas.annotations.media.Schema;
6 import lombok.Data; 8 import lombok.Data;
7 9
@@ -36,19 +38,26 @@ public class CrmFollowUpRecordRespVO { @@ -36,19 +38,26 @@ public class CrmFollowUpRecordRespVO {
36 38
37 @Schema(description = "关联的商机编号数组") 39 @Schema(description = "关联的商机编号数组")
38 private List<Long> businessIds; 40 private List<Long> businessIds;
39 - @Schema(description = "关联的商机名称数组")  
40 - private List<String> businessNames; 41 + @Schema(description = "关联的商机数组")
  42 + private List<CrmBusinessRespVO> businesses;
41 43
42 @Schema(description = "关联的联系人编号数组") 44 @Schema(description = "关联的联系人编号数组")
43 private List<Long> contactIds; 45 private List<Long> contactIds;
44 @Schema(description = "关联的联系人名称数组") 46 @Schema(description = "关联的联系人名称数组")
45 - private List<String> contactNames; 47 + private List<CrmBusinessRespVO> contacts;
46 48
47 @Schema(description = "图片") 49 @Schema(description = "图片")
48 private List<String> picUrls; 50 private List<String> picUrls;
49 @Schema(description = "附件") 51 @Schema(description = "附件")
50 private List<String> fileUrls; 52 private List<String> fileUrls;
51 53
  54 + @Schema(description = "创建人", example = "1024")
  55 + @ExcelProperty("创建人")
  56 + private String creator;
  57 + @Schema(description = "创建人名字", example = "芋道源码")
  58 + @ExcelProperty("创建人名字")
  59 + private String creatorName;
  60 +
52 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 61 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
53 private LocalDateTime createTime; 62 private LocalDateTime createTime;
54 63
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java
@@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogPageReqVO; 6 import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogPageReqVO;
7 -import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogV2RespVO; 7 +import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogRespVO;
8 import cn.iocoder.yudao.module.crm.enums.LogRecordConstants; 8 import cn.iocoder.yudao.module.crm.enums.LogRecordConstants;
9 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; 9 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
10 import cn.iocoder.yudao.module.system.api.logger.OperateLogApi; 10 import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
@@ -41,7 +41,7 @@ public class CrmOperateLogController { @@ -41,7 +41,7 @@ public class CrmOperateLogController {
41 private static final Map<Integer, String> BIZ_TYPE_MAP = new HashMap<>(); 41 private static final Map<Integer, String> BIZ_TYPE_MAP = new HashMap<>();
42 42
43 static { 43 static {
44 - BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_LEADS.getType(), CRM_LEADS_TYPE); 44 + BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CLUE.getType(), CRM_CLUE_TYPE);
45 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CUSTOMER.getType(), CRM_CUSTOMER_TYPE); 45 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CUSTOMER.getType(), CRM_CUSTOMER_TYPE);
46 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CONTACT.getType(), CRM_CONTACT_TYPE); 46 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CONTACT.getType(), CRM_CONTACT_TYPE);
47 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_BUSINESS.getType(), CRM_BUSINESS_TYPE); 47 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_BUSINESS.getType(), CRM_BUSINESS_TYPE);
@@ -54,11 +54,11 @@ public class CrmOperateLogController { @@ -54,11 +54,11 @@ public class CrmOperateLogController {
54 @GetMapping("/page") 54 @GetMapping("/page")
55 @Operation(summary = "获得操作日志") 55 @Operation(summary = "获得操作日志")
56 @PreAuthorize("@ss.hasPermission('crm:operate-log:query')") 56 @PreAuthorize("@ss.hasPermission('crm:operate-log:query')")
57 - public CommonResult<PageResult<CrmOperateLogV2RespVO>> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) { 57 + public CommonResult<PageResult<CrmOperateLogRespVO>> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) {
58 OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO(); 58 OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO();
59 reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页,需要分页需注释 59 reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页,需要分页需注释
60 reqDTO.setBizType(BIZ_TYPE_MAP.get(pageReqVO.getBizType())).setBizId(pageReqVO.getBizId()); 60 reqDTO.setBizType(BIZ_TYPE_MAP.get(pageReqVO.getBizType())).setBizId(pageReqVO.getBizId());
61 - return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO), CrmOperateLogV2RespVO.class)); 61 + return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO), CrmOperateLogRespVO.class));
62 } 62 }
63 63
64 } 64 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogRespVO.java
@@ -6,10 +6,10 @@ import lombok.Data; @@ -6,10 +6,10 @@ import lombok.Data;
6 6
7 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
8 8
9 -@Schema(description = "管理后台 - CRM 跟进 Response VO") 9 +@Schema(description = "管理后台 - CRM 操作日志 Response VO")
10 @Data 10 @Data
11 @ExcelIgnoreUnannotated 11 @ExcelIgnoreUnannotated
12 -public class CrmOperateLogV2RespVO { 12 +public class CrmOperateLogRespVO {
13 13
14 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") 14 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
15 private Long id; 15 private Long id;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java
@@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.permission; @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.permission;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO; 8 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO;
8 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO; 9 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO;
9 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; 10 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
10 -import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert;  
11 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; 11 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
12 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; 12 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
13 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; 13 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
@@ -19,6 +19,7 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; @@ -19,6 +19,7 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
19 import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; 19 import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
20 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 20 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
21 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 21 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  22 +import com.google.common.collect.Multimaps;
22 import io.swagger.v3.oas.annotations.Operation; 23 import io.swagger.v3.oas.annotations.Operation;
23 import io.swagger.v3.oas.annotations.Parameter; 24 import io.swagger.v3.oas.annotations.Parameter;
24 import io.swagger.v3.oas.annotations.Parameters; 25 import io.swagger.v3.oas.annotations.Parameters;
@@ -29,11 +30,16 @@ import org.springframework.security.access.prepost.PreAuthorize; @@ -29,11 +30,16 @@ import org.springframework.security.access.prepost.PreAuthorize;
29 import org.springframework.validation.annotation.Validated; 30 import org.springframework.validation.annotation.Validated;
30 import org.springframework.web.bind.annotation.*; 31 import org.springframework.web.bind.annotation.*;
31 32
32 -import java.util.*; 33 +import java.util.Collection;
  34 +import java.util.Collections;
  35 +import java.util.List;
  36 +import java.util.Map;
33 import java.util.stream.Stream; 37 import java.util.stream.Stream;
34 38
35 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 39 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
36 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 40 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  41 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
  42 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
37 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 43 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
38 44
39 @Tag(name = "管理后台 - CRM 数据权限") 45 @Tag(name = "管理后台 - CRM 数据权限")
@@ -98,18 +104,32 @@ public class CrmPermissionController { @@ -98,18 +104,32 @@ public class CrmPermissionController {
98 @PreAuthorize("@ss.hasPermission('crm:permission:query')") 104 @PreAuthorize("@ss.hasPermission('crm:permission:query')")
99 public CommonResult<List<CrmPermissionRespVO>> getPermissionList(@RequestParam("bizType") Integer bizType, 105 public CommonResult<List<CrmPermissionRespVO>> getPermissionList(@RequestParam("bizType") Integer bizType,
100 @RequestParam("bizId") Long bizId) { 106 @RequestParam("bizId") Long bizId) {
101 - List<CrmPermissionDO> permission = permissionService.getPermissionListByBiz(bizType, bizId);  
102 - if (CollUtil.isEmpty(permission)) { 107 + List<CrmPermissionDO> permissions = permissionService.getPermissionListByBiz(bizType, bizId);
  108 + if (CollUtil.isEmpty(permissions)) {
103 return success(Collections.emptyList()); 109 return success(Collections.emptyList());
104 } 110 }
105 111
  112 + // 查询相关数据
  113 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  114 + convertSet(permissions, CrmPermissionDO::getUserId));
  115 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  116 + Map<Long, PostRespDTO> postMap = postApi.getPostMap(
  117 + convertSetByFlatMap(userMap.values(), AdminUserRespDTO::getPostIds,
  118 + item -> item != null ? item.stream() : Stream.empty()));
106 // 拼接数据 119 // 拼接数据
107 - List<AdminUserRespDTO> userList = adminUserApi.getUserList(convertSet(permission, CrmPermissionDO::getUserId));  
108 - Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId));  
109 - Set<Long> postIds = CollectionUtils.convertSetByFlatMap(userList, AdminUserRespDTO::getPostIds,  
110 - item -> item != null ? item.stream() : Stream.empty());  
111 - Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);  
112 - return success(CrmPermissionConvert.INSTANCE.convert(permission, userList, deptMap, postMap)); 120 + return success(CollectionUtils.convertList(BeanUtils.toBean(permissions, CrmPermissionRespVO.class), item -> {
  121 + findAndThen(userMap, item.getUserId(), user -> {
  122 + item.setNickname(user.getNickname());
  123 + findAndThen(deptMap, user.getDeptId(), deptRespDTO -> item.setDeptName(deptRespDTO.getName()));
  124 + if (CollUtil.isEmpty(user.getPostIds())) {
  125 + item.setPostNames(Collections.emptySet());
  126 + return;
  127 + }
  128 + List<PostRespDTO> postList = MapUtils.getList(Multimaps.forMap(postMap), user.getPostIds());
  129 + item.setPostNames(CollectionUtils.convertSet(postList, PostRespDTO::getName));
  130 + });
  131 + return item;
  132 + }));
113 } 133 }
114 134
115 } 135 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java
1 package cn.iocoder.yudao.module.crm.controller.admin.product; 1 package cn.iocoder.yudao.module.crm.controller.admin.product;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 import cn.iocoder.yudao.framework.common.pojo.PageParam; 6 import cn.iocoder.yudao.framework.common.pojo.PageParam;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 -import cn.iocoder.yudao.framework.common.util.collection.SetUtils; 8 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  9 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 11 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; 12 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO;
11 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO; 13 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO;
12 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO; 14 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
13 -import cn.iocoder.yudao.module.crm.convert.product.CrmProductConvert;  
14 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; 15 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
15 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; 16 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
16 import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService; 17 import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService;
@@ -34,10 +35,9 @@ import java.util.Map; @@ -34,10 +35,9 @@ import java.util.Map;
34 import java.util.stream.Stream; 35 import java.util.stream.Stream;
35 36
36 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 37 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
37 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;  
38 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; 38 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
39 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; 39 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
40 -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 40 +import static java.util.Collections.singletonList;
41 41
42 @Tag(name = "管理后台 - CRM 产品") 42 @Tag(name = "管理后台 - CRM 产品")
43 @RestController 43 @RestController
@@ -49,6 +49,7 @@ public class CrmProductController { @@ -49,6 +49,7 @@ public class CrmProductController {
49 private CrmProductService productService; 49 private CrmProductService productService;
50 @Resource 50 @Resource
51 private CrmProductCategoryService productCategoryService; 51 private CrmProductCategoryService productCategoryService;
  52 +
52 @Resource 53 @Resource
53 private AdminUserApi adminUserApi; 54 private AdminUserApi adminUserApi;
54 55
@@ -82,21 +83,30 @@ public class CrmProductController { @@ -82,21 +83,30 @@ public class CrmProductController {
82 @PreAuthorize("@ss.hasPermission('crm:product:query')") 83 @PreAuthorize("@ss.hasPermission('crm:product:query')")
83 public CommonResult<CrmProductRespVO> getProduct(@RequestParam("id") Long id) { 84 public CommonResult<CrmProductRespVO> getProduct(@RequestParam("id") Long id) {
84 CrmProductDO product = productService.getProduct(id); 85 CrmProductDO product = productService.getProduct(id);
  86 + return success(buildProductDetail(product));
  87 + }
  88 +
  89 + private CrmProductRespVO buildProductDetail(CrmProductDO product) {
85 if (product == null) { 90 if (product == null) {
86 - return success(null); 91 + return null;
87 } 92 }
88 - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(  
89 - SetUtils.asSet(Long.valueOf(product.getCreator()), product.getOwnerUserId()));  
90 - CrmProductCategoryDO category = productCategoryService.getProductCategory(product.getCategoryId());  
91 - return success(CrmProductConvert.INSTANCE.convert(product, userMap, category)); 93 + return buildProductDetailList(singletonList(product)).get(0);
  94 + }
  95 +
  96 + @GetMapping("/simple-list")
  97 + @Operation(summary = "获得产品精简列表", description = "只包含被开启的产品,主要用于前端的下拉选项")
  98 + public CommonResult<List<CrmProductRespVO>> getProductSimpleList() {
  99 + List<CrmProductDO> list = productService.getProductListByStatus(CommonStatusEnum.ENABLE.getStatus());
  100 + return success(convertList(list, product -> new CrmProductRespVO().setId(product.getId()).setName(product.getName())
  101 + .setUnit(product.getUnit()).setNo(product.getNo()).setPrice(product.getPrice())));
92 } 102 }
93 103
94 @GetMapping("/page") 104 @GetMapping("/page")
95 @Operation(summary = "获得产品分页") 105 @Operation(summary = "获得产品分页")
96 @PreAuthorize("@ss.hasPermission('crm:product:query')") 106 @PreAuthorize("@ss.hasPermission('crm:product:query')")
97 public CommonResult<PageResult<CrmProductRespVO>> getProductPage(@Valid CrmProductPageReqVO pageVO) { 107 public CommonResult<PageResult<CrmProductRespVO>> getProductPage(@Valid CrmProductPageReqVO pageVO) {
98 - PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO, getLoginUserId());  
99 - return success(new PageResult<>(getProductDetailList(pageResult.getList()), pageResult.getTotal())); 108 + PageResult<CrmProductDO> pageResult = productService.getProductPage(pageVO);
  109 + return success(new PageResult<>(buildProductDetailList(pageResult.getList()), pageResult.getTotal()));
100 } 110 }
101 111
102 @GetMapping("/export-excel") 112 @GetMapping("/export-excel")
@@ -106,21 +116,30 @@ public class CrmProductController { @@ -106,21 +116,30 @@ public class CrmProductController {
106 public void exportProductExcel(@Valid CrmProductPageReqVO exportReqVO, 116 public void exportProductExcel(@Valid CrmProductPageReqVO exportReqVO,
107 HttpServletResponse response) throws IOException { 117 HttpServletResponse response) throws IOException {
108 exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); 118 exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
109 - List<CrmProductDO> list = productService.getProductPage(exportReqVO, getLoginUserId()).getList(); 119 + List<CrmProductDO> list = productService.getProductPage(exportReqVO).getList();
110 // 导出 Excel 120 // 导出 Excel
111 ExcelUtils.write(response, "产品.xls", "数据", CrmProductRespVO.class, 121 ExcelUtils.write(response, "产品.xls", "数据", CrmProductRespVO.class,
112 - getProductDetailList(list)); 122 + buildProductDetailList(list));
113 } 123 }
114 124
115 - private List<CrmProductRespVO> getProductDetailList(List<CrmProductDO> list) { 125 + private List<CrmProductRespVO> buildProductDetailList(List<CrmProductDO> list) {
116 if (CollUtil.isEmpty(list)) { 126 if (CollUtil.isEmpty(list)) {
117 return Collections.emptyList(); 127 return Collections.emptyList();
118 } 128 }
  129 + // 1.1 获得用户信息
119 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( 130 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
120 convertSetByFlatMap(list, user -> Stream.of(Long.valueOf(user.getCreator()), user.getOwnerUserId()))); 131 convertSetByFlatMap(list, user -> Stream.of(Long.valueOf(user.getCreator()), user.getOwnerUserId())));
121 - List<CrmProductCategoryDO> productCategoryList = productCategoryService.getProductCategoryList( 132 + // 1.2 获得分类信息
  133 + Map<Long, CrmProductCategoryDO> categoryMap = productCategoryService.getProductCategoryMap(
122 convertSet(list, CrmProductDO::getCategoryId)); 134 convertSet(list, CrmProductDO::getCategoryId));
123 - return CrmProductConvert.INSTANCE.convertList(list, userMap, productCategoryList); 135 + // 2. 拼接数据
  136 + return BeanUtils.toBean(list, CrmProductRespVO.class, productVO -> {
  137 + // 2.1 设置用户信息
  138 + MapUtils.findAndThen(userMap, productVO.getOwnerUserId(), user -> productVO.setOwnerUserName(user.getNickname()));
  139 + MapUtils.findAndThen(userMap, Long.valueOf(productVO.getCreator()), user -> productVO.setCreatorName(user.getNickname()));
  140 + // 2.2 设置分类名称
  141 + MapUtils.findAndThen(categoryMap, productVO.getCategoryId(), category -> productVO.setCategoryName(category.getName()));
  142 + });
124 } 143 }
125 144
126 } 145 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java
@@ -8,6 +8,7 @@ import com.alibaba.excel.annotation.ExcelProperty; @@ -8,6 +8,7 @@ import com.alibaba.excel.annotation.ExcelProperty;
8 import io.swagger.v3.oas.annotations.media.Schema; 8 import io.swagger.v3.oas.annotations.media.Schema;
9 import lombok.Data; 9 import lombok.Data;
10 10
  11 +import java.math.BigDecimal;
11 import java.time.LocalDateTime; 12 import java.time.LocalDateTime;
12 13
13 @Schema(description = "管理后台 - CRM 产品 Response VO") 14 @Schema(description = "管理后台 - CRM 产品 Response VO")
@@ -34,7 +35,7 @@ public class CrmProductRespVO { @@ -34,7 +35,7 @@ public class CrmProductRespVO {
34 35
35 @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") 36 @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
36 @ExcelProperty("价格,单位:分") 37 @ExcelProperty("价格,单位:分")
37 - private Long price; 38 + private BigDecimal price;
38 39
39 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架") 40 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架")
40 @ExcelProperty(value = "单位", converter = DictConvert.class) 41 @ExcelProperty(value = "单位", converter = DictConvert.class)
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java
@@ -7,6 +7,8 @@ import io.swagger.v3.oas.annotations.media.Schema; @@ -7,6 +7,8 @@ import io.swagger.v3.oas.annotations.media.Schema;
7 import jakarta.validation.constraints.NotNull; 7 import jakarta.validation.constraints.NotNull;
8 import lombok.Data; 8 import lombok.Data;
9 9
  10 +import java.math.BigDecimal;
  11 +
10 @Schema(description = "管理后台 - CRM 产品创建/修改 Request VO") 12 @Schema(description = "管理后台 - CRM 产品创建/修改 Request VO")
11 @Data 13 @Data
12 public class CrmProductSaveReqVO { 14 public class CrmProductSaveReqVO {
@@ -28,10 +30,10 @@ public class CrmProductSaveReqVO { @@ -28,10 +30,10 @@ public class CrmProductSaveReqVO {
28 @DiffLogField(name = "单位", function = CrmProductUnitParseFunction.NAME) 30 @DiffLogField(name = "单位", function = CrmProductUnitParseFunction.NAME)
29 private Integer unit; 31 private Integer unit;
30 32
31 - @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") 33 + @Schema(description = "价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
32 @NotNull(message = "价格不能为空") 34 @NotNull(message = "价格不能为空")
33 @DiffLogField(name = "价格") 35 @DiffLogField(name = "价格")
34 - private Long price; 36 + private BigDecimal price;
35 37
36 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架") 38 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架")
37 @NotNull(message = "状态不能为空") 39 @NotNull(message = "状态不能为空")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
@@ -4,20 +4,23 @@ import cn.hutool.core.collection.CollUtil; @@ -4,20 +4,23 @@ import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  9 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 11 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; 12 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; 13 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
12 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; 14 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
13 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO;  
14 -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert; 15 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO;
15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 16 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 17 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
17 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; 18 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
18 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; 19 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
19 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; 20 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
20 import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService; 21 import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
  22 +import cn.iocoder.yudao.module.system.api.dept.DeptApi;
  23 +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
21 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 24 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
22 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 25 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
23 import io.swagger.v3.oas.annotations.Operation; 26 import io.swagger.v3.oas.annotations.Operation;
@@ -31,14 +34,15 @@ import org.springframework.validation.annotation.Validated; @@ -31,14 +34,15 @@ import org.springframework.validation.annotation.Validated;
31 import org.springframework.web.bind.annotation.*; 34 import org.springframework.web.bind.annotation.*;
32 35
33 import java.io.IOException; 36 import java.io.IOException;
  37 +import java.util.Collections;
34 import java.util.List; 38 import java.util.List;
35 import java.util.Map; 39 import java.util.Map;
36 import java.util.stream.Stream; 40 import java.util.stream.Stream;
37 41
38 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 42 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
39 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; 43 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
40 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;  
41 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 44 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  45 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
42 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; 46 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
43 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 47 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
44 48
@@ -57,18 +61,20 @@ public class CrmReceivableController { @@ -57,18 +61,20 @@ public class CrmReceivableController {
57 61
58 @Resource 62 @Resource
59 private AdminUserApi adminUserApi; 63 private AdminUserApi adminUserApi;
  64 + @Resource
  65 + private DeptApi deptApi;
60 66
61 @PostMapping("/create") 67 @PostMapping("/create")
62 @Operation(summary = "创建回款") 68 @Operation(summary = "创建回款")
63 @PreAuthorize("@ss.hasPermission('crm:receivable:create')") 69 @PreAuthorize("@ss.hasPermission('crm:receivable:create')")
64 - public CommonResult<Long> createReceivable(@Valid @RequestBody CrmReceivableCreateReqVO createReqVO) {  
65 - return success(receivableService.createReceivable(createReqVO, getLoginUserId())); 70 + public CommonResult<Long> createReceivable(@Valid @RequestBody CrmReceivableSaveReqVO createReqVO) {
  71 + return success(receivableService.createReceivable(createReqVO));
66 } 72 }
67 73
68 @PutMapping("/update") 74 @PutMapping("/update")
69 @Operation(summary = "更新回款") 75 @Operation(summary = "更新回款")
70 @PreAuthorize("@ss.hasPermission('crm:receivable:update')") 76 @PreAuthorize("@ss.hasPermission('crm:receivable:update')")
71 - public CommonResult<Boolean> updateReceivable(@Valid @RequestBody CrmReceivableUpdateReqVO updateReqVO) { 77 + public CommonResult<Boolean> updateReceivable(@Valid @RequestBody CrmReceivableSaveReqVO updateReqVO) {
72 receivableService.updateReceivable(updateReqVO); 78 receivableService.updateReceivable(updateReqVO);
73 return success(true); 79 return success(true);
74 } 80 }
@@ -88,7 +94,14 @@ public class CrmReceivableController { @@ -88,7 +94,14 @@ public class CrmReceivableController {
88 @PreAuthorize("@ss.hasPermission('crm:receivable:query')") 94 @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
89 public CommonResult<CrmReceivableRespVO> getReceivable(@RequestParam("id") Long id) { 95 public CommonResult<CrmReceivableRespVO> getReceivable(@RequestParam("id") Long id) {
90 CrmReceivableDO receivable = receivableService.getReceivable(id); 96 CrmReceivableDO receivable = receivableService.getReceivable(id);
91 - return success(CrmReceivableConvert.INSTANCE.convert(receivable)); 97 + return success(buildReceivableDetail(receivable));
  98 + }
  99 +
  100 + private CrmReceivableRespVO buildReceivableDetail(CrmReceivableDO receivable) {
  101 + if (receivable == null) {
  102 + return null;
  103 + }
  104 + return buildReceivableDetailList(Collections.singletonList(receivable)).get(0);
92 } 105 }
93 106
94 @GetMapping("/page") 107 @GetMapping("/page")
@@ -96,7 +109,7 @@ public class CrmReceivableController { @@ -96,7 +109,7 @@ public class CrmReceivableController {
96 @PreAuthorize("@ss.hasPermission('crm:receivable:query')") 109 @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
97 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) { 110 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) {
98 PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(pageReqVO, getLoginUserId()); 111 PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(pageReqVO, getLoginUserId());
99 - return success(buildReceivableDetailPage(pageResult)); 112 + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
100 } 113 }
101 114
102 @GetMapping("/page-by-customer") 115 @GetMapping("/page-by-customer")
@@ -104,10 +117,9 @@ public class CrmReceivableController { @@ -104,10 +117,9 @@ public class CrmReceivableController {
104 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) { 117 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) {
105 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空"); 118 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
106 PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePageByCustomerId(pageReqVO); 119 PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePageByCustomerId(pageReqVO);
107 - return success(buildReceivableDetailPage(pageResult)); 120 + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
108 } 121 }
109 122
110 - // TODO 芋艿:后面在优化导出  
111 @GetMapping("/export-excel") 123 @GetMapping("/export-excel")
112 @Operation(summary = "导出回款 Excel") 124 @Operation(summary = "导出回款 Excel")
113 @PreAuthorize("@ss.hasPermission('crm:receivable:export')") 125 @PreAuthorize("@ss.hasPermission('crm:receivable:export')")
@@ -115,33 +127,56 @@ public class CrmReceivableController { @@ -115,33 +127,56 @@ public class CrmReceivableController {
115 public void exportReceivableExcel(@Valid CrmReceivablePageReqVO exportReqVO, 127 public void exportReceivableExcel(@Valid CrmReceivablePageReqVO exportReqVO,
116 HttpServletResponse response) throws IOException { 128 HttpServletResponse response) throws IOException {
117 exportReqVO.setPageSize(PAGE_SIZE_NONE); 129 exportReqVO.setPageSize(PAGE_SIZE_NONE);
118 - PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(exportReqVO, getLoginUserId()); 130 + List<CrmReceivableDO> list = receivableService.getReceivablePage(exportReqVO, getLoginUserId()).getList();
119 // 导出 Excel 131 // 导出 Excel
120 ExcelUtils.write(response, "回款.xls", "数据", CrmReceivableRespVO.class, 132 ExcelUtils.write(response, "回款.xls", "数据", CrmReceivableRespVO.class,
121 - buildReceivableDetailPage(pageResult).getList()); 133 + buildReceivableDetailList(list));
122 } 134 }
123 135
124 - /**  
125 - * 构建详细的回款分页结果  
126 - *  
127 - * @param pageResult 简单的回款分页结果  
128 - * @return 详细的回款分页结果  
129 - */  
130 - private PageResult<CrmReceivableRespVO> buildReceivableDetailPage(PageResult<CrmReceivableDO> pageResult) {  
131 - List<CrmReceivableDO> receivableList = pageResult.getList(); 136 + private List<CrmReceivableRespVO> buildReceivableDetailList(List<CrmReceivableDO> receivableList) {
132 if (CollUtil.isEmpty(receivableList)) { 137 if (CollUtil.isEmpty(receivableList)) {
133 - return PageResult.empty(pageResult.getTotal()); 138 + return Collections.emptyList();
134 } 139 }
135 - // 1. 获取客户列表  
136 - List<CrmCustomerDO> customerList = customerService.getCustomerList( 140 + // 1.1 获取客户列表
  141 + Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
137 convertSet(receivableList, CrmReceivableDO::getCustomerId)); 142 convertSet(receivableList, CrmReceivableDO::getCustomerId));
138 - // 2. 获取创建人、负责人列表 143 + // 1.2 获取创建人、负责人列表
139 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivableList, 144 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivableList,
140 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); 145 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
141 - // 3. 获得合同列表  
142 - List<CrmContractDO> contractList = contractService.getContractList( 146 + Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId));
  147 + // 1.3 获得合同列表
  148 + Map<Long, CrmContractDO> contractMap = contractService.getContractMap(
143 convertSet(receivableList, CrmReceivableDO::getContractId)); 149 convertSet(receivableList, CrmReceivableDO::getContractId));
144 - return CrmReceivableConvert.INSTANCE.convertPage(pageResult, userMap, customerList, contractList); 150 + // 2. 拼接结果
  151 + return BeanUtils.toBean(receivableList, CrmReceivableRespVO.class, (receivableVO) -> {
  152 + // 2.1 拼接客户名称
  153 + findAndThen(customerMap, receivableVO.getCustomerId(), customer -> receivableVO.setCustomerName(customer.getName()));
  154 + // 2.2 拼接负责人、创建人名称
  155 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(receivableVO.getCreator()),
  156 + user -> receivableVO.setCreatorName(user.getNickname()));
  157 + MapUtils.findAndThen(userMap, receivableVO.getOwnerUserId(), user -> {
  158 + receivableVO.setOwnerUserName(user.getNickname());
  159 + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> receivableVO.setOwnerUserDeptName(dept.getName()));
  160 + });
  161 + // 2.3 拼接合同信息
  162 + findAndThen(contractMap, receivableVO.getContractId(), contract ->
  163 + receivableVO.setContract(BeanUtils.toBean(contract, CrmContractRespVO.class)));
  164 + });
  165 + }
  166 +
  167 + @PutMapping("/submit")
  168 + @Operation(summary = "提交回款审批")
  169 + @PreAuthorize("@ss.hasPermission('crm:receivable:update')")
  170 + public CommonResult<Boolean> submitContract(@RequestParam("id") Long id) {
  171 + receivableService.submitReceivable(id, getLoginUserId());
  172 + return success(true);
  173 + }
  174 +
  175 + @GetMapping("/audit-count")
  176 + @Operation(summary = "获得待审核回款数量")
  177 + @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
  178 + public CommonResult<Long> getAuditReceivableCount() {
  179 + return success(receivableService.getAuditReceivableCount(getLoginUserId()));
145 } 180 }
146 181
147 } 182 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
@@ -5,13 +5,13 @@ import cn.hutool.core.lang.Assert; @@ -5,13 +5,13 @@ import cn.hutool.core.lang.Assert;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;  
11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; 11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
12 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO; 12 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
13 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO;  
14 -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert; 13 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO;
  14 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
17 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; 17 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
@@ -24,24 +24,25 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; @@ -24,24 +24,25 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
24 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 24 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
25 import io.swagger.v3.oas.annotations.Operation; 25 import io.swagger.v3.oas.annotations.Operation;
26 import io.swagger.v3.oas.annotations.Parameter; 26 import io.swagger.v3.oas.annotations.Parameter;
  27 +import io.swagger.v3.oas.annotations.Parameters;
27 import io.swagger.v3.oas.annotations.tags.Tag; 28 import io.swagger.v3.oas.annotations.tags.Tag;
28 import jakarta.annotation.Resource; 29 import jakarta.annotation.Resource;
29 import jakarta.servlet.http.HttpServletResponse; 30 import jakarta.servlet.http.HttpServletResponse;
30 import jakarta.validation.Valid; 31 import jakarta.validation.Valid;
31 -import org.springframework.context.annotation.Lazy;  
32 import org.springframework.security.access.prepost.PreAuthorize; 32 import org.springframework.security.access.prepost.PreAuthorize;
33 import org.springframework.validation.annotation.Validated; 33 import org.springframework.validation.annotation.Validated;
34 import org.springframework.web.bind.annotation.*; 34 import org.springframework.web.bind.annotation.*;
35 35
36 import java.io.IOException; 36 import java.io.IOException;
  37 +import java.util.Collections;
37 import java.util.List; 38 import java.util.List;
38 import java.util.Map; 39 import java.util.Map;
39 import java.util.stream.Stream; 40 import java.util.stream.Stream;
40 41
41 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 42 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
42 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; 43 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
43 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;  
44 -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; 44 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  45 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
45 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; 46 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
46 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 47 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
47 48
@@ -56,7 +57,6 @@ public class CrmReceivablePlanController { @@ -56,7 +57,6 @@ public class CrmReceivablePlanController {
56 @Resource 57 @Resource
57 private CrmReceivableService receivableService; 58 private CrmReceivableService receivableService;
58 @Resource 59 @Resource
59 - @Lazy  
60 private CrmContractService contractService; 60 private CrmContractService contractService;
61 @Resource 61 @Resource
62 private CrmCustomerService customerService; 62 private CrmCustomerService customerService;
@@ -67,14 +67,14 @@ public class CrmReceivablePlanController { @@ -67,14 +67,14 @@ public class CrmReceivablePlanController {
67 @PostMapping("/create") 67 @PostMapping("/create")
68 @Operation(summary = "创建回款计划") 68 @Operation(summary = "创建回款计划")
69 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:create')") 69 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:create')")
70 - public CommonResult<Long> createReceivablePlan(@Valid @RequestBody CrmReceivablePlanCreateReqVO createReqVO) {  
71 - return success(receivablePlanService.createReceivablePlan(createReqVO, getLoginUserId())); 70 + public CommonResult<Long> createReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO createReqVO) {
  71 + return success(receivablePlanService.createReceivablePlan(createReqVO));
72 } 72 }
73 73
74 @PutMapping("/update") 74 @PutMapping("/update")
75 @Operation(summary = "更新回款计划") 75 @Operation(summary = "更新回款计划")
76 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:update')") 76 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:update')")
77 - public CommonResult<Boolean> updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanUpdateReqVO updateReqVO) { 77 + public CommonResult<Boolean> updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO updateReqVO) {
78 receivablePlanService.updateReceivablePlan(updateReqVO); 78 receivablePlanService.updateReceivablePlan(updateReqVO);
79 return success(true); 79 return success(true);
80 } 80 }
@@ -94,7 +94,14 @@ public class CrmReceivablePlanController { @@ -94,7 +94,14 @@ public class CrmReceivablePlanController {
94 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')") 94 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
95 public CommonResult<CrmReceivablePlanRespVO> getReceivablePlan(@RequestParam("id") Long id) { 95 public CommonResult<CrmReceivablePlanRespVO> getReceivablePlan(@RequestParam("id") Long id) {
96 CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(id); 96 CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(id);
97 - return success(CrmReceivablePlanConvert.INSTANCE.convert(receivablePlan)); 97 + return success(buildReceivablePlanDetail(receivablePlan));
  98 + }
  99 +
  100 + private CrmReceivablePlanRespVO buildReceivablePlanDetail(CrmReceivablePlanDO receivablePlan) {
  101 + if (receivablePlan == null) {
  102 + return null;
  103 + }
  104 + return buildReceivableDetailList(Collections.singletonList(receivablePlan)).get(0);
98 } 105 }
99 106
100 @GetMapping("/page") 107 @GetMapping("/page")
@@ -102,7 +109,7 @@ public class CrmReceivablePlanController { @@ -102,7 +109,7 @@ public class CrmReceivablePlanController {
102 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')") 109 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
103 public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPage(@Valid CrmReceivablePlanPageReqVO pageReqVO) { 110 public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPage(@Valid CrmReceivablePlanPageReqVO pageReqVO) {
104 PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(pageReqVO, getLoginUserId()); 111 PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(pageReqVO, getLoginUserId());
105 - return success(convertDetailReceivablePlanPage(pageResult)); 112 + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
106 } 113 }
107 114
108 @GetMapping("/page-by-customer") 115 @GetMapping("/page-by-customer")
@@ -110,10 +117,9 @@ public class CrmReceivablePlanController { @@ -110,10 +117,9 @@ public class CrmReceivablePlanController {
110 public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPageByCustomer(@Valid CrmReceivablePlanPageReqVO pageReqVO) { 117 public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPageByCustomer(@Valid CrmReceivablePlanPageReqVO pageReqVO) {
111 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空"); 118 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
112 PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO); 119 PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO);
113 - return success(convertDetailReceivablePlanPage(pageResult)); 120 + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal()));
114 } 121 }
115 122
116 - // TODO 芋艿:后面在优化导出  
117 @GetMapping("/export-excel") 123 @GetMapping("/export-excel")
118 @Operation(summary = "导出回款计划 Excel") 124 @Operation(summary = "导出回款计划 Excel")
119 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:export')") 125 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:export')")
@@ -121,36 +127,64 @@ public class CrmReceivablePlanController { @@ -121,36 +127,64 @@ public class CrmReceivablePlanController {
121 public void exportReceivablePlanExcel(@Valid CrmReceivablePlanPageReqVO exportReqVO, 127 public void exportReceivablePlanExcel(@Valid CrmReceivablePlanPageReqVO exportReqVO,
122 HttpServletResponse response) throws IOException { 128 HttpServletResponse response) throws IOException {
123 exportReqVO.setPageSize(PAGE_SIZE_NONE); 129 exportReqVO.setPageSize(PAGE_SIZE_NONE);
124 - PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId()); 130 + List<CrmReceivablePlanDO> list = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId()).getList();
125 // 导出 Excel 131 // 导出 Excel
126 ExcelUtils.write(response, "回款计划.xls", "数据", CrmReceivablePlanRespVO.class, 132 ExcelUtils.write(response, "回款计划.xls", "数据", CrmReceivablePlanRespVO.class,
127 - convertDetailReceivablePlanPage(pageResult).getList()); 133 + buildReceivableDetailList(list));
128 } 134 }
129 135
130 - /**  
131 - * 构建详细的回款计划分页结果  
132 - *  
133 - * @param pageResult 简单的回款计划分页结果  
134 - * @return 详细的回款计划分页结果  
135 - */  
136 - private PageResult<CrmReceivablePlanRespVO> convertDetailReceivablePlanPage(PageResult<CrmReceivablePlanDO> pageResult) {  
137 - List<CrmReceivablePlanDO> receivablePlanList = pageResult.getList(); 136 + private List<CrmReceivablePlanRespVO> buildReceivableDetailList(List<CrmReceivablePlanDO> receivablePlanList) {
138 if (CollUtil.isEmpty(receivablePlanList)) { 137 if (CollUtil.isEmpty(receivablePlanList)) {
139 - return PageResult.empty(pageResult.getTotal()); 138 + return Collections.emptyList();
140 } 139 }
141 - // 1. 获取客户列表  
142 - List<CrmCustomerDO> customerList = customerService.getCustomerList( 140 + // 1.1 获取客户 Map
  141 + Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
143 convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId)); 142 convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId));
144 - // 2. 获取创建人、负责人列表 143 + // 1.2 获取创建人、负责人列表
145 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivablePlanList, 144 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivablePlanList,
146 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); 145 contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId())));
147 - // 3. 获得合同列表  
148 - List<CrmContractDO> contractList = contractService.getContractList( 146 + // 1.3 获得合同 Map
  147 + Map<Long, CrmContractDO> contractMap = contractService.getContractMap(
149 convertSet(receivablePlanList, CrmReceivablePlanDO::getContractId)); 148 convertSet(receivablePlanList, CrmReceivablePlanDO::getContractId));
150 - // 4. 获得还款列表  
151 - List<CrmReceivableDO> receivableList = receivableService.getReceivableList( 149 + // 1.4 获得回款 Map
  150 + Map<Long, CrmReceivableDO> receivableMap = receivableService.getReceivableMap(
152 convertSet(receivablePlanList, CrmReceivablePlanDO::getReceivableId)); 151 convertSet(receivablePlanList, CrmReceivablePlanDO::getReceivableId));
153 - return CrmReceivablePlanConvert.INSTANCE.convertPage(pageResult, userMap, customerList, contractList, receivableList); 152 + // 2. 拼接数据
  153 + return BeanUtils.toBean(receivablePlanList, CrmReceivablePlanRespVO.class, (receivablePlanVO) -> {
  154 + // 2.1 拼接客户信息
  155 + findAndThen(customerMap, receivablePlanVO.getCustomerId(), customer -> receivablePlanVO.setCustomerName(customer.getName()));
  156 + // 2.2 拼接用户信息
  157 + findAndThen(userMap, receivablePlanVO.getOwnerUserId(), user -> receivablePlanVO.setOwnerUserName(user.getNickname()));
  158 + findAndThen(userMap, Long.parseLong(receivablePlanVO.getCreator()), user -> receivablePlanVO.setCreatorName(user.getNickname()));
  159 + // 2.3 拼接合同信息
  160 + findAndThen(contractMap, receivablePlanVO.getContractId(), contract -> receivablePlanVO.setContractNo(contract.getNo()));
  161 + // 2.4 拼接回款信息
  162 + receivablePlanVO.setReceivable(BeanUtils.toBean(receivableMap.get(receivablePlanVO.getReceivableId()), CrmReceivableRespVO.class));
  163 + });
  164 + }
  165 +
  166 + @GetMapping("/simple-list")
  167 + @Operation(summary = "获得回款计划精简列表", description = "获得回款计划精简列表,主要用于前端的下拉选项")
  168 + @Parameters({
  169 + @Parameter(name = "customerId", description = "客户编号", required = true),
  170 + @Parameter(name = "contractId", description = "合同编号", required = true)
  171 + })
  172 + @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
  173 + public CommonResult<List<CrmReceivablePlanRespVO>> getReceivablePlanSimpleList(@RequestParam("customerId") Long customerId,
  174 + @RequestParam("contractId") Long contractId) {
  175 + CrmReceivablePlanPageReqVO pageReqVO = new CrmReceivablePlanPageReqVO().setCustomerId(customerId).setContractId(contractId);
  176 + pageReqVO.setPageNo(PAGE_SIZE_NONE);
  177 + PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO);
  178 + return success(convertList(pageResult.getList(), receivablePlan -> new CrmReceivablePlanRespVO() // 只返回 id、period 等信息
  179 + .setId(receivablePlan.getId()).setPeriod(receivablePlan.getPeriod()).setReceivableId(receivablePlan.getReceivableId())
  180 + .setPrice(receivablePlan.getPrice()).setReturnType(receivablePlan.getReturnType())));
  181 + }
  182 +
  183 + @GetMapping("/remind-count")
  184 + @Operation(summary = "获得待回款提醒数量")
  185 + @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
  186 + public CommonResult<Long> getReceivablePlanRemindCount() {
  187 + return success(receivablePlanService.getReceivablePlanRemindCount(getLoginUserId()));
154 } 188 }
155 189
156 } 190 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanBaseVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import lombok.Data;  
5 -import org.springframework.format.annotation.DateTimeFormat;  
6 -  
7 -import java.time.LocalDateTime;  
8 -  
9 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;  
10 -  
11 -/**  
12 - * 回款计划 Base VO,提供给添加、修改、详细的子 VO 使用  
13 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成  
14 - */  
15 -@Data  
16 -public class CrmReceivablePlanBaseVO {  
17 -  
18 - @Schema(description = "期数", example = "1")  
19 - private Integer period;  
20 -  
21 - @Schema(description = "回款计划编号", example = "19852")  
22 - private Long receivableId;  
23 -  
24 - @Schema(description = "计划回款金额", example = "29675")  
25 - private Integer price;  
26 -  
27 - @Schema(description = "计划回款日期")  
28 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
29 - private LocalDateTime returnTime;  
30 -  
31 - @Schema(description = "提前几天提醒")  
32 - private Integer remindDays;  
33 -  
34 - @Schema(description = "提醒日期")  
35 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
36 - private LocalDateTime remindTime;  
37 -  
38 - @Schema(description = "客户名称", example = "18026")  
39 - private Long customerId;  
40 -  
41 - @Schema(description = "合同编号", example = "3473")  
42 - private Long contractId;  
43 -  
44 - // TODO @liuhongfeng:负责人编号  
45 - @Schema(description = "负责人编号", example = "17828")  
46 - private Long ownerUserId;  
47 -  
48 - @Schema(description = "显示顺序")  
49 - private Integer sort;  
50 -  
51 - @Schema(description = "备注", example = "备注")  
52 - private String remark;  
53 -  
54 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanCreateReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;  
2 -  
3 -import lombok.*;  
4 -import io.swagger.v3.oas.annotations.media.Schema;  
5 -  
6 -@Schema(description = "管理后台 - CRM 回款计划创建 Request VO")  
7 -@Data  
8 -@EqualsAndHashCode(callSuper = true)  
9 -@ToString(callSuper = true)  
10 -public class CrmReceivablePlanCreateReqVO extends CrmReceivablePlanBaseVO {  
11 -  
12 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java
@@ -30,8 +30,10 @@ public class CrmReceivablePlanPageReqVO extends PageParam { @@ -30,8 +30,10 @@ public class CrmReceivablePlanPageReqVO extends PageParam {
30 @Schema(description = "客户编号", example = "18026") 30 @Schema(description = "客户编号", example = "18026")
31 private Long customerId; 31 private Long customerId;
32 32
33 - // TODO @芋艿:这个搜的应该是合同编号 no  
34 - @Schema(description = "合同名称", example = "3473") 33 + @Schema(description = "合同编号", example = "H3473")
  34 + private String contractNo;
  35 +
  36 + @Schema(description = "合同编号", example = "3473")
35 private Long contractId; 37 private Long contractId;
36 38
37 @Schema(description = "场景类型", example = "1") 39 @Schema(description = "场景类型", example = "1")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
2 2
  3 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
  4 +import com.alibaba.excel.annotation.ExcelProperty;
3 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
4 -import lombok.*; 6 +import lombok.Data;
  7 +
  8 +import java.math.BigDecimal;
5 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
6 10
  11 +// TODO @puhui999:缺导出
7 @Schema(description = "管理后台 - CRM 回款计划 Response VO") 12 @Schema(description = "管理后台 - CRM 回款计划 Response VO")
8 @Data 13 @Data
9 -@EqualsAndHashCode(callSuper = true)  
10 -@ToString(callSuper = true)  
11 -public class CrmReceivablePlanRespVO extends CrmReceivablePlanBaseVO { 14 +public class CrmReceivablePlanRespVO {
12 15
13 - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25153") 16 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
14 private Long id; 17 private Long id;
15 18
16 - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)  
17 - private LocalDateTime createTime; 19 + @Schema(description = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  20 + private Integer period;
18 21
  22 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  23 + private Long customerId;
19 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test") 24 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
20 private String customerName; 25 private String customerName;
21 26
  27 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  28 + private Long contractId;
22 @Schema(description = "合同编号", example = "Q110") 29 @Schema(description = "合同编号", example = "Q110")
23 private String contractNo; 30 private String contractNo;
24 31
  32 + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  33 + private Long ownerUserId;
25 @Schema(description = "负责人", example = "test") 34 @Schema(description = "负责人", example = "test")
26 private String ownerUserName; 35 private String ownerUserName;
27 36
28 - @Schema(description = "创建人", example = "25682")  
29 - private String creator; 37 + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  38 + private LocalDateTime returnTime;
30 39
31 - @Schema(description = "创建人名字", example = "test")  
32 - private String creatorName; 40 + @Schema(description = "计划回款方式", example = "1")
  41 + private Integer returnType;
33 42
34 - @Schema(description = "完成状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")  
35 - private Boolean finishStatus; 43 + @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  44 + private BigDecimal price;
36 45
37 - @Schema(description = "回款方式", example = "1") // 来自 Receivable 的 returnType 字段  
38 - private Integer returnType; 46 + @Schema(description = "回款编号", example = "19852")
  47 + private Long receivableId;
  48 + @Schema(description = "回款信息")
  49 + private CrmReceivableRespVO receivable;
  50 +
  51 + @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  52 + private Integer remindDays;
  53 +
  54 + @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  55 + private LocalDateTime remindTime;
  56 +
  57 + @Schema(description = "备注", example = "备注")
  58 + private String remark;
  59 +
  60 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  61 + @ExcelProperty("创建时间")
  62 + private LocalDateTime createTime;
  63 +
  64 + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  65 + @ExcelProperty("更新时间")
  66 + private LocalDateTime updateTime;
  67 +
  68 + @Schema(description = "创建人", example = "1024")
  69 + @ExcelProperty("创建人")
  70 + private String creator;
  71 + @Schema(description = "创建人名字", example = "芋道源码")
  72 + @ExcelProperty("创建人名字")
  73 + private String creatorName;
39 74
40 } 75 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +import java.math.BigDecimal;
  8 +import java.time.LocalDateTime;
  9 +
  10 +@Schema(description = "管理后台 - CRM 回款计划新增/修改 Request VO")
  11 +@Data
  12 +public class CrmReceivablePlanSaveReqVO {
  13 +
  14 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  15 + private Long id;
  16 +
  17 + @Schema(description = "客户编号", hidden = true, example = "2")
  18 + private Long customerId; // 该字段不通过前端传递,而是 contractId 查询出来设置进去
  19 +
  20 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  21 + @NotNull(message = "合同编号不能为空")
  22 + private Long contractId;
  23 +
  24 + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  25 + @NotNull(message = "负责人编号不能为空")
  26 + private Long ownerUserId;
  27 +
  28 + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  29 + @NotNull(message = "计划回款日期不能为空")
  30 + private LocalDateTime returnTime;
  31 +
  32 + @Schema(description = "回款方式", example = "1")
  33 + private Integer returnType;
  34 +
  35 + @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  36 + @NotNull(message = "计划回款金额不能为空")
  37 + private BigDecimal price;
  38 +
  39 + @Schema(description = "提前几天提醒", example = "1")
  40 + private Integer remindDays;
  41 +
  42 + @Schema(description = "备注", example = "备注")
  43 + private String remark;
  44 +
  45 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanUpdateReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import lombok.*;  
5 -  
6 -import jakarta.validation.constraints.*;  
7 -  
8 -@Schema(description = "管理后台 - CRM 回款计划更新 Request VO")  
9 -@Data  
10 -@EqualsAndHashCode(callSuper = true)  
11 -@ToString(callSuper = true)  
12 -public class CrmReceivablePlanUpdateReqVO extends CrmReceivablePlanBaseVO {  
13 -  
14 - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25153")  
15 - @NotNull(message = "ID不能为空")  
16 - private Long id;  
17 -  
18 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableBaseVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;  
2 -  
3 -import cn.iocoder.yudao.framework.common.validation.InEnum;  
4 -import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;  
5 -import io.swagger.v3.oas.annotations.media.Schema;  
6 -import lombok.Data;  
7 -import org.springframework.format.annotation.DateTimeFormat;  
8 -  
9 -import java.time.LocalDateTime;  
10 -  
11 -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;  
12 -  
13 -/**  
14 - * 回款 Base VO,提供给添加、修改、详细的子 VO 使用  
15 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成  
16 - */  
17 -@Data  
18 -public class CrmReceivableBaseVO {  
19 -  
20 - @Schema(description = "回款编号",requiredMode = Schema.RequiredMode.REQUIRED, example = "31177")  
21 - private String no;  
22 -  
23 - // TODO @liuhongfeng:回款计划编号  
24 - @Schema(description = "回款计划", example = "31177")  
25 - private Long planId;  
26 -  
27 - // TODO @liuhongfeng:客户编号  
28 - @Schema(description = "客户名称", example = "4963")  
29 - private Long customerId;  
30 -  
31 - // TODO @liuhongfeng:客户编号  
32 - @Schema(description = "合同名称", example = "30305")  
33 - private Long contractId;  
34 -  
35 - // TODO @liuhongfeng:这个字段,应该不是前端传递的噢,而是后端自己生成的  
36 - @Schema(description = "审批状态", example = "1")  
37 - @InEnum(CrmAuditStatusEnum.class)  
38 - private Integer checkStatus;  
39 -  
40 - @Schema(description = "回款日期")  
41 - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)  
42 - private LocalDateTime returnTime;  
43 -  
44 - @Schema(description = "回款方式", example = "2")  
45 - private Integer returnType;  
46 -  
47 - @Schema(description = "回款金额,单位:分", example = "31859")  
48 - private Integer price;  
49 -  
50 - // TODO @liuhongfeng:负责人编号  
51 - @Schema(description = "负责人", example = "22202")  
52 - private Long ownerUserId;  
53 -  
54 - @Schema(description = "显示顺序")  
55 - private Integer sort;  
56 -  
57 - @Schema(description = "备注", example = "备注")  
58 - private String remark;  
59 -  
60 -  
61 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableCreateReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;  
2 -  
3 -import lombok.*;  
4 -import io.swagger.v3.oas.annotations.media.Schema;  
5 -  
6 -@Schema(description = "管理后台 - CRM 回款创建 Request VO")  
7 -@Data  
8 -@EqualsAndHashCode(callSuper = true)  
9 -@ToString(callSuper = true)  
10 -public class CrmReceivableCreateReqVO extends CrmReceivableBaseVO {  
11 -  
12 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java
@@ -24,6 +24,9 @@ public class CrmReceivablePageReqVO extends PageParam { @@ -24,6 +24,9 @@ public class CrmReceivablePageReqVO extends PageParam {
24 @Schema(description = "客户编号", example = "4963") 24 @Schema(description = "客户编号", example = "4963")
25 private Long customerId; 25 private Long customerId;
26 26
  27 + @Schema(description = "合同编号", example = "4963")
  28 + private Long contractId;
  29 +
27 @Schema(description = "场景类型", example = "1") 30 @Schema(description = "场景类型", example = "1")
28 @InEnum(CrmSceneTypeEnum.class) 31 @InEnum(CrmSceneTypeEnum.class)
29 private Integer sceneType; // 场景类型,为 null 时则表示全部 32 private Integer sceneType; // 场景类型,为 null 时则表示全部
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
2 2
  3 +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO;
  4 +import com.alibaba.excel.annotation.ExcelProperty;
3 import io.swagger.v3.oas.annotations.media.Schema; 5 import io.swagger.v3.oas.annotations.media.Schema;
4 -import lombok.*; 6 +import lombok.Data;
  7 +
  8 +import java.math.BigDecimal;
5 import java.time.LocalDateTime; 9 import java.time.LocalDateTime;
6 10
7 // TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能 11 // TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能
8 @Schema(description = "管理后台 - CRM 回款 Response VO") 12 @Schema(description = "管理后台 - CRM 回款 Response VO")
9 @Data 13 @Data
10 -@EqualsAndHashCode(callSuper = true)  
11 -@ToString(callSuper = true)  
12 -public class CrmReceivableRespVO extends CrmReceivableBaseVO { 14 +public class CrmReceivableRespVO {
13 15
14 - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25787") 16 + @Schema(description = "编号", example = "25787")
15 private Long id; 17 private Long id;
16 18
17 - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)  
18 - private LocalDateTime createTime; 19 + @Schema(description = "回款编号", example = "31177")
  20 + private String no;
  21 +
  22 + @Schema(description = "回款计划编号", example = "1024")
  23 + private Long planId;
19 24
  25 + @Schema(description = "回款方式", example = "2")
  26 + private Integer returnType;
  27 +
  28 + @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  29 + private BigDecimal price;
  30 +
  31 + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  32 + private LocalDateTime returnTime;
  33 +
  34 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  35 + private Long customerId;
20 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test") 36 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
21 private String customerName; 37 private String customerName;
22 38
  39 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  40 + private Long contractId;
  41 + @Schema(description = "合同信息")
  42 + private CrmContractRespVO contract;
  43 +
  44 + @Schema(description = "负责人的用户编号", example = "25682")
  45 + @ExcelProperty("负责人的用户编号")
  46 + private Long ownerUserId;
  47 + @Schema(description = "负责人名字", example = "25682")
  48 + @ExcelProperty("负责人名字")
  49 + private String ownerUserName;
  50 + @Schema(description = "负责人部门")
  51 + @ExcelProperty("负责人部门")
  52 + private String ownerUserDeptName;
  53 +
  54 + @Schema(description = "工作流编号", example = "1043")
  55 + @ExcelProperty("工作流编号")
  56 + private String processInstanceId;
  57 +
23 @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") 58 @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
24 private Integer auditStatus; 59 private Integer auditStatus;
25 60
26 - @Schema(description = "合同编号", example = "Q110")  
27 - private String contractNo; 61 + @Schema(description = "备注", example = "备注")
  62 + private String remark;
28 63
29 - @Schema(description = "负责人", example = "test")  
30 - private String ownerUserName; 64 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  65 + private LocalDateTime createTime;
  66 +
  67 + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
  68 + private LocalDateTime updateTime;
31 69
32 @Schema(description = "创建人", example = "25682") 70 @Schema(description = "创建人", example = "25682")
33 private String creator; 71 private String creator;
34 -  
35 @Schema(description = "创建人名字", example = "test") 72 @Schema(description = "创建人名字", example = "test")
36 private String creatorName; 73 private String creatorName;
37 74
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
  2 +
  3 +import cn.iocoder.yudao.framework.common.validation.InEnum;
  4 +import cn.iocoder.yudao.module.crm.enums.receivable.CrmReceivableReturnTypeEnum;
  5 +import cn.iocoder.yudao.module.crm.framework.operatelog.core.*;
  6 +import com.mzt.logapi.starter.annotation.DiffLogField;
  7 +import io.swagger.v3.oas.annotations.media.Schema;
  8 +import jakarta.validation.constraints.NotNull;
  9 +import lombok.Data;
  10 +
  11 +import java.math.BigDecimal;
  12 +import java.time.LocalDateTime;
  13 +
  14 +@Schema(description = "管理后台 - CRM 回款新增/修改 Request VO")
  15 +@Data
  16 +public class CrmReceivableSaveReqVO {
  17 +
  18 + @Schema(description = "编号", example = "25787")
  19 + private Long id;
  20 +
  21 + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  22 + @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME)
  23 + @NotNull(message = "负责人编号不能为空")
  24 + private Long ownerUserId;
  25 +
  26 + @Schema(description = "客户编号", example = "2")
  27 + @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME)
  28 + private Long customerId; // 该字段不通过前端传递,而是 contractId 查询出来设置进去
  29 +
  30 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  31 + @DiffLogField(name = "合同", function = CrmContractParseFunction.NAME)
  32 + @NotNull(message = "合同编号不能为空")
  33 + private Long contractId;
  34 +
  35 + @Schema(description = "回款计划编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  36 + @DiffLogField(name = "合同", function = CrmReceivablePlanParseFunction.NAME)
  37 + private Long planId;
  38 +
  39 + @Schema(description = "回款方式", example = "2")
  40 + @DiffLogField(name = "回款方式", function = CrmReceivableReturnTypeParseFunction.NAME)
  41 + @InEnum(CrmReceivableReturnTypeEnum.class)
  42 + private Integer returnType;
  43 +
  44 + @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  45 + @DiffLogField(name = "回款金额")
  46 + @NotNull(message = "回款金额不能为空")
  47 + private BigDecimal price;
  48 +
  49 + @Schema(description = "回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  50 + @NotNull(message = "回款日期不能为空")
  51 + @DiffLogField(name = "回款日期")
  52 + private LocalDateTime returnTime;
  53 +
  54 + @Schema(description = "备注", example = "备注")
  55 + @DiffLogField(name = "备注")
  56 + private String remark;
  57 +
  58 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableUpdateReqVO.java deleted
1 -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;  
2 -  
3 -import io.swagger.v3.oas.annotations.media.Schema;  
4 -import lombok.*;  
5 -  
6 -import jakarta.validation.constraints.*;  
7 -  
8 -@Schema(description = "管理后台 - CRM 回款更新 Request VO")  
9 -@Data  
10 -@EqualsAndHashCode(callSuper = true)  
11 -@ToString(callSuper = true)  
12 -public class CrmReceivableUpdateReqVO extends CrmReceivableBaseVO {  
13 -  
14 - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25787")  
15 - @NotNull(message = "ID不能为空")  
16 - private Long id;  
17 -  
18 -}  
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.http renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.http
1 ### 合同金额排行榜 1 ### 合同金额排行榜
2 -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 2 +GET {{baseUrl}}/crm/statistics-rank/get-contract-price-rank?deptId=100&times[0]=2022-12-12 00:00:00&times[1]=2024-12-12 23:59:59
3 Authorization: Bearer {{token}} 3 Authorization: Bearer {{token}}
4 tenant-id: {{adminTenentId}} 4 tenant-id: {{adminTenentId}}
5 5
6 ### 回款金额排行榜 6 ### 回款金额排行榜
7 -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 7 +GET {{baseUrl}}/crm/statistics-rank/get-receivable-price-rank?deptId=100&times[0]=2022-12-12 00:00:00&times[1]=2024-12-12 23:59:59
8 Authorization: Bearer {{token}} 8 Authorization: Bearer {{token}}
9 tenant-id: {{adminTenentId}} 9 tenant-id: {{adminTenentId}}
10 \ No newline at end of file 10 \ No newline at end of file
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.bi; 1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 -import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO;  
5 -import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO;  
6 -import cn.iocoder.yudao.module.crm.service.bi.CrmBiRankingService; 4 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO;
  5 +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO;
  6 +import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsRankingService;
7 import io.swagger.v3.oas.annotations.Operation; 7 import io.swagger.v3.oas.annotations.Operation;
8 import io.swagger.v3.oas.annotations.tags.Tag; 8 import io.swagger.v3.oas.annotations.tags.Tag;
9 import jakarta.annotation.Resource; 9 import jakarta.annotation.Resource;
@@ -19,68 +19,68 @@ import java.util.List; @@ -19,68 +19,68 @@ import java.util.List;
19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 19 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
20 20
21 21
22 -@Tag(name = "管理后台 - CRM BI 排行榜") 22 +@Tag(name = "管理后台 - CRM 排行榜统计")
23 @RestController 23 @RestController
24 -@RequestMapping("/crm/bi-rank") 24 +@RequestMapping("/crm/statistics-rank")
25 @Validated 25 @Validated
26 -public class CrmBiRankController { 26 +public class CrmStatisticsRankController {
27 27
28 @Resource 28 @Resource
29 - private CrmBiRankingService rankingService; 29 + private CrmStatisticsRankingService rankingService;
30 30
31 @GetMapping("/get-contract-price-rank") 31 @GetMapping("/get-contract-price-rank")
32 @Operation(summary = "获得合同金额排行榜") 32 @Operation(summary = "获得合同金额排行榜")
33 - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")  
34 - public CommonResult<List<CrmBiRanKRespVO>> getContractPriceRank(@Valid CrmBiRankReqVO rankingReqVO) { 33 + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
  34 + public CommonResult<List<CrmStatisticsRanKRespVO>> getContractPriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
35 return success(rankingService.getContractPriceRank(rankingReqVO)); 35 return success(rankingService.getContractPriceRank(rankingReqVO));
36 } 36 }
37 37
38 @GetMapping("/get-receivable-price-rank") 38 @GetMapping("/get-receivable-price-rank")
39 @Operation(summary = "获得回款金额排行榜") 39 @Operation(summary = "获得回款金额排行榜")
40 - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")  
41 - public CommonResult<List<CrmBiRanKRespVO>> getReceivablePriceRank(@Valid CrmBiRankReqVO rankingReqVO) { 40 + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
  41 + public CommonResult<List<CrmStatisticsRanKRespVO>> getReceivablePriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
42 return success(rankingService.getReceivablePriceRank(rankingReqVO)); 42 return success(rankingService.getReceivablePriceRank(rankingReqVO));
43 } 43 }
44 44
45 @GetMapping("/get-contract-count-rank") 45 @GetMapping("/get-contract-count-rank")
46 @Operation(summary = "获得签约合同数量排行榜") 46 @Operation(summary = "获得签约合同数量排行榜")
47 - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")  
48 - public CommonResult<List<CrmBiRanKRespVO>> getContractCountRank(@Valid CrmBiRankReqVO rankingReqVO) { 47 + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
  48 + public CommonResult<List<CrmStatisticsRanKRespVO>> getContractCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
49 return success(rankingService.getContractCountRank(rankingReqVO)); 49 return success(rankingService.getContractCountRank(rankingReqVO));
50 } 50 }
51 51
52 @GetMapping("/get-product-sales-rank") 52 @GetMapping("/get-product-sales-rank")
53 @Operation(summary = "获得产品销量排行榜") 53 @Operation(summary = "获得产品销量排行榜")
54 - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")  
55 - public CommonResult<List<CrmBiRanKRespVO>> getProductSalesRank(@Valid CrmBiRankReqVO rankingReqVO) { 54 + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
  55 + public CommonResult<List<CrmStatisticsRanKRespVO>> getProductSalesRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
56 return success(rankingService.getProductSalesRank(rankingReqVO)); 56 return success(rankingService.getProductSalesRank(rankingReqVO));
57 } 57 }
58 58
59 @GetMapping("/get-customer-count-rank") 59 @GetMapping("/get-customer-count-rank")
60 @Operation(summary = "获得新增客户数排行榜") 60 @Operation(summary = "获得新增客户数排行榜")
61 - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")  
62 - public CommonResult<List<CrmBiRanKRespVO>> getCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) { 61 + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
  62 + public CommonResult<List<CrmStatisticsRanKRespVO>> getCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
63 return success(rankingService.getCustomerCountRank(rankingReqVO)); 63 return success(rankingService.getCustomerCountRank(rankingReqVO));
64 } 64 }
65 65
66 @GetMapping("/get-contacts-count-rank") 66 @GetMapping("/get-contacts-count-rank")
67 @Operation(summary = "获得新增联系人数排行榜") 67 @Operation(summary = "获得新增联系人数排行榜")
68 - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")  
69 - public CommonResult<List<CrmBiRanKRespVO>> getContactsCountRank(@Valid CrmBiRankReqVO rankingReqVO) { 68 + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
  69 + public CommonResult<List<CrmStatisticsRanKRespVO>> getContactsCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
70 return success(rankingService.getContactsCountRank(rankingReqVO)); 70 return success(rankingService.getContactsCountRank(rankingReqVO));
71 } 71 }
72 72
73 @GetMapping("/get-follow-count-rank") 73 @GetMapping("/get-follow-count-rank")
74 @Operation(summary = "获得跟进次数排行榜") 74 @Operation(summary = "获得跟进次数排行榜")
75 - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")  
76 - public CommonResult<List<CrmBiRanKRespVO>> getFollowCountRank(@Valid CrmBiRankReqVO rankingReqVO) { 75 + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
  76 + public CommonResult<List<CrmStatisticsRanKRespVO>> getFollowCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
77 return success(rankingService.getFollowCountRank(rankingReqVO)); 77 return success(rankingService.getFollowCountRank(rankingReqVO));
78 } 78 }
79 79
80 @GetMapping("/get-follow-customer-count-rank") 80 @GetMapping("/get-follow-customer-count-rank")
81 @Operation(summary = "获得跟进客户数排行榜") 81 @Operation(summary = "获得跟进客户数排行榜")
82 - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')")  
83 - public CommonResult<List<CrmBiRanKRespVO>> getFollowCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) { 82 + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')")
  83 + public CommonResult<List<CrmStatisticsRanKRespVO>> getFollowCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) {
84 return success(rankingService.getFollowCustomerCountRank(rankingReqVO)); 84 return success(rankingService.getFollowCustomerCountRank(rankingReqVO));
85 } 85 }
86 86
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRanKRespVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRanKRespVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.bi.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import lombok.Data; 4 import lombok.Data;
5 5
6 6
7 -@Schema(description = "管理后台 - CRM BI 排行榜 Response VO") 7 +@Schema(description = "管理后台 - CRM BI 排行榜统计 Response VO")
8 @Data 8 @Data
9 -public class CrmBiRanKRespVO { 9 +public class CrmStatisticsRanKRespVO {
10 10
11 @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 11 @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
12 private Long ownerUserId; 12 private Long ownerUserId;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRankReqVO.java renamed to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRankReqVO.java
1 -package cn.iocoder.yudao.module.crm.controller.admin.bi.vo; 1 +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 import jakarta.validation.constraints.NotEmpty; 4 import jakarta.validation.constraints.NotEmpty;
@@ -11,9 +11,9 @@ import java.util.List; @@ -11,9 +11,9 @@ import java.util.List;
11 11
12 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; 12 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
13 13
14 -@Schema(description = "管理后台 - CRM BI 排行榜 Request VO") 14 +@Schema(description = "管理后台 - CRM 排行榜统计 Request VO")
15 @Data 15 @Data
16 -public class CrmBiRankReqVO { 16 +public class CrmStatisticsRankReqVO {
17 17
18 @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") 18 @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
19 @NotNull(message = "部门 id 不能为空") 19 @NotNull(message = "部门 id 不能为空")