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 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 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 265 if (CollUtil.isEmpty(from)) {
266 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 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 17 import java.util.function.Function;
18 18  
19 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 71 * @param id 区域编号
71 72 * @return 区域
72 73 */
73   - public static Area getArea(Integer id) {
  74 + public static Area parseArea(Integer id) {
74 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 132 * @param id 区域编号
... ... @@ -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 150 * @param separator 分隔符
99 151 * @return 格式化后的区域
100 152 */
... ... @@ -141,7 +193,7 @@ public class AreaUtils {
141 193 */
142 194 public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) {
143 195 for (int i = 0; i < Byte.MAX_VALUE; i++) {
144   - Area area = AreaUtils.getArea(id);
  196 + Area area = AreaUtils.parseArea(id);
145 197 if (area == null) {
146 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 72 * @return 地区
73 73 */
74 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 82 * @return 地区
83 83 */
84 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 17 @Test
18 18 public void testGetArea() {
19 19 // 调用:北京
20   - Area area = AreaUtils.getArea(110100);
  20 + Area area = AreaUtils.parseArea(110100);
21 21 // 断言
22 22 assertEquals(area.getId(), 110100);
23 23 assertEquals(area.getName(), "北京市");
... ...
yudao-framework/yudao-spring-boot-starter-excel/pom.xml
... ... @@ -46,6 +46,11 @@
46 46 <groupId>com.alibaba</groupId>
47 47 <artifactId>easyexcel</artifactId>
48 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 54 </dependencies>
50 55  
51 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 3 import lombok.AllArgsConstructor;
4 4 import lombok.Getter;
5 5  
  6 +// TODO @puhui999:列表有办法通过 field name 么?主要考虑一个点,可能导入模版的顺序可能会变
6 7 /**
7   - * Crm 数据权限角色枚举
  8 + * Excel 列名枚举
  9 + * 默认枚举 26 列列名如果有需求更多的列名请自行补充
8 10 *
9 11 * @author HUIHUI
10 12 */
11 13 @Getter
12 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 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 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 6 import com.alibaba.excel.EasyExcel;
4 7 import com.alibaba.excel.converters.longconverter.LongStringConverter;
5 8 import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
  9 +import jakarta.servlet.http.HttpServletResponse;
6 10 import org.springframework.web.multipart.MultipartFile;
7 11  
8   -import jakarta.servlet.http.HttpServletResponse;
9 12 import java.io.IOException;
10 13 import java.net.URLEncoder;
11 14 import java.nio.charset.StandardCharsets;
... ... @@ -21,29 +24,46 @@ public class ExcelUtils {
21 24 /**
22 25 * 将列表以 Excel 响应给前端
23 26 *
24   - * @param response 响应
25   - * @param filename 文件名
  27 + * @param response 响应
  28 + * @param filename 文件名
26 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 33 * @throws IOException 写入失败的情况
31 34 */
32 35 public static <T> void write(HttpServletResponse response, String filename, String sheetName,
33 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 53 // 输出 Excel
35 54 EasyExcel.write(response.getOutputStream(), head)
36 55 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
37 56 .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
  57 + .registerWriteHandler(new SelectSheetWriteHandler(selectMap)) // 基于固定 sheet 实现下拉框
38 58 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
39 59 .sheet(sheetName).doWrite(data);
40 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 62 response.setContentType("application/vnd.ms-excel;charset=UTF-8");
43 63 }
44 64  
45 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 67 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
48 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 4 import lombok.Data;
5 5 import org.springframework.context.ApplicationEvent;
6 6  
7   -import jakarta.validation.constraints.NotNull;
8   -
9 7 /**
10 8 * 流程实例的结果发生变化的 Event
11 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 3 import cn.hutool.core.util.StrUtil;
4 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 import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO;
8 8 import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO;
9 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 11 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
12 12 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
13 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 1 package cn.iocoder.yudao.module.bpm.framework.bpm.core.event;
2 2  
  3 +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent;
3 4 import lombok.AllArgsConstructor;
4 5 import org.springframework.context.ApplicationEventPublisher;
5 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 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 5 import cn.iocoder.yudao.module.bpm.service.oa.BpmOALeaveService;
6 6 import cn.iocoder.yudao.module.bpm.service.oa.BpmOALeaveServiceImpl;
7 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 14 String CRM_PRODUCT_UNIT = "crm_product_unit"; // CRM 产品单位
15 15 String CRM_PRODUCT_STATUS = "crm_product_status"; // CRM 产品状态
16 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 11  
12 12 // ========== 合同管理 1-020-000-000 ==========
13 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 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 19 // ========== 线索管理 1-020-001-000 ==========
18 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 23 // ========== 商机管理 1-020-002-000 ==========
23 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 29 // ========== 联系人管理 1-020-003-000 ==========
30 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 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 34 // ========== 回款 1-020-004-000 ==========
35 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 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 49 // ========== 客户管理 1_020_006_000 ==========
41 50 ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在");
... ... @@ -53,14 +62,13 @@ public interface ErrorCodeConstants {
53 62 ErrorCode CUSTOMER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_020_006_012, "导入客户数据不能为空!");
54 63 ErrorCode CUSTOMER_CREATE_NAME_NOT_NULL = new ErrorCode(1_020_006_013, "客户名称不能为空!");
55 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 67 // ========== 权限管理 1_020_007_000 ==========
58 68 ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");
59 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 70 ErrorCode CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS = new ErrorCode(1_020_007_003, "{}操作失败,原因:转移对象已经是该负责人");
62 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 72 ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限");
65 73 ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人");
66 74 ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限");
... ... @@ -68,6 +76,7 @@ public interface ErrorCodeConstants {
68 76 // ========== 产品 1_020_008_000 ==========
69 77 ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在");
70 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 81 // ========== 产品分类 1_020_009_000 ==========
73 82 ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_020_009_000, "产品分类不存在");
... ... @@ -77,21 +86,18 @@ public interface ErrorCodeConstants {
77 86 ErrorCode PRODUCT_CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1_020_009_004, "父分类不能是二级分类");
78 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 96 // ========== 客户公海规则设置 1_020_012_000 ==========
88   - ErrorCode CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_020_012_000, "客户公海配置不存在或未启用");
89 97 ErrorCode CUSTOMER_LIMIT_CONFIG_NOT_EXISTS = new ErrorCode(1_020_012_001, "客户限制配置不存在");
90 98  
91 99 // ========== 跟进记录 1_020_013_000 ==========
92 100 ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在");
93 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 8 */
9 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 27 // ======================= CRM_CUSTOMER 客户 =======================
26 28  
... ... @@ -41,6 +43,10 @@ public interface LogRecordConstants {
41 43 String CRM_CUSTOMER_RECEIVE_SUCCESS = "{{#ownerUserName != null ? '将客户【' + #customer.name + '】分配给【' + #ownerUserName + '】' : '领取客户【' + #customer.name + '】'}}";
42 44 String CRM_CUSTOMER_IMPORT_SUB_TYPE = "{{#isUpdate ? '导入并更新客户' : '导入客户'}}";
43 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 51 // ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 =======================
46 52  
... ... @@ -69,6 +75,10 @@ public interface LogRecordConstants {
69 75 String CRM_CONTACT_DELETE_SUCCESS = "删除了联系人【{{#contactName}}】";
70 76 String CRM_CONTACT_TRANSFER_SUB_TYPE = "转移联系人";
71 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 83 // ======================= CRM_BUSINESS 商机 =======================
74 84  
... ... @@ -81,6 +91,16 @@ public interface LogRecordConstants {
81 91 String CRM_BUSINESS_DELETE_SUCCESS = "删除了商机【{{#businessName}}】";
82 92 String CRM_BUSINESS_TRANSFER_SUB_TYPE = "转移商机";
83 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 105 // ======================= CRM_CONTRACT 合同 =======================
86 106  
... ... @@ -95,6 +115,8 @@ public interface LogRecordConstants {
95 115 String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{getAdminUserById{#contract.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】";
96 116 String CRM_CONTRACT_SUBMIT_SUB_TYPE = "提交合同审批";
97 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 121 // ======================= CRM_PRODUCT 产品 =======================
100 122  
... ... @@ -125,6 +147,8 @@ public interface LogRecordConstants {
125 147 String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}";
126 148 String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款";
127 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 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 1 package cn.iocoder.yudao.module.crm.enums.business;
2 2  
3   -import cn.hutool.core.util.ObjectUtil;
4 3 import cn.iocoder.yudao.framework.common.core.IntArrayValuable;
5 4 import lombok.Getter;
6 5 import lombok.RequiredArgsConstructor;
7 6  
8 7 import java.util.Arrays;
9 8  
10   -// TODO @lzxhqs:1)title、description、create 可以删除,非标准的 javadoc 注释哈,然后可以在类上加下这个类的注释;2)CrmBizEndStatus 改成 CrmBusinessEndStatus,非必要不缩写哈,可阅读比较重要
11 9 /**
  10 + * 商机的结束状态枚举
  11 + *
12 12 * @author lzxhqs
13   - * @version 1.0
14   - * @title CrmBizEndStatus
15   - * @description
16   - * @create 2024/1/12
17 13 */
18 14 @RequiredArgsConstructor
19 15 @Getter
20   -public enum CrmBizEndStatus implements IntArrayValuable {
  16 +public enum CrmBusinessEndStatusEnum implements IntArrayValuable {
21 17  
22 18 WIN(1, "赢单"),
23 19 LOSE(2, "输单"),
24 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 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 17 @Getter
18 18 public enum CrmBizTypeEnum implements IntArrayValuable {
19 19  
20   - CRM_LEADS(1, "线索"),
  20 + CRM_CLUE(1, "线索"),
21 21 CRM_CUSTOMER(2, "客户"),
22 22 CRM_CONTACT(3, "联系人"),
23 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 10 /**
11 11 * CRM 数据权限级别枚举
12 12 *
  13 + * OWNER > WRITE > READ
  14 + *
13 15 * @author HUIHUI
14 16 */
15 17 @Getter
... ... @@ -17,8 +19,8 @@ import java.util.Arrays;
17 19 public enum CrmPermissionLevelEnum implements IntArrayValuable {
18 20  
19 21 OWNER(1, "负责人"),
20   - READ(2, "读"),
21   - WRITE(3, "写");
  22 + READ(2, "只读"),
  23 + WRITE(3, "读写");
22 24  
23 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 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 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 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 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 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
8 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 12 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
  13 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
15 14 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO;
16 15 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO;
17 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
  17 +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
18 18 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
19 19 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService;
20   -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusTypeService;
21 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 26 import io.swagger.v3.oas.annotations.Operation;
23 27 import io.swagger.v3.oas.annotations.Parameter;
24 28 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -30,13 +34,15 @@ import org.springframework.validation.annotation.Validated;
30 34 import org.springframework.web.bind.annotation.*;
31 35  
32 36 import java.io.IOException;
  37 +import java.util.Collections;
33 38 import java.util.List;
  39 +import java.util.Map;
  40 +import java.util.stream.Stream;
34 41  
35 42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
36 43 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
37 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 46 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
41 47 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
42 48 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS;
... ... @@ -52,9 +58,16 @@ public class CrmBusinessController {
52 58 @Resource
53 59 private CrmCustomerService customerService;
54 60 @Resource
55   - private CrmBusinessStatusTypeService businessStatusTypeService;
  61 + private CrmBusinessStatusService businessStatusTypeService;
56 62 @Resource
57 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 72 @PostMapping("/create")
60 73 @Operation(summary = "创建商机")
... ... @@ -71,6 +84,14 @@ public class CrmBusinessController {
71 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 95 @DeleteMapping("/delete")
75 96 @Operation(summary = "删除商机")
76 97 @Parameter(name = "id", description = "编号", required = true)
... ... @@ -86,15 +107,23 @@ public class CrmBusinessController {
86 107 @PreAuthorize("@ss.hasPermission('crm:business:query')")
87 108 public CommonResult<CrmBusinessRespVO> getBusiness(@RequestParam("id") Long id) {
88 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 129 @GetMapping("/simple-all-list")
... ... @@ -105,7 +134,8 @@ public class CrmBusinessController {
105 134 reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
106 135 PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(reqVO, getLoginUserId());
107 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 141 @GetMapping("/page")
... ... @@ -113,7 +143,7 @@ public class CrmBusinessController {
113 143 @PreAuthorize("@ss.hasPermission('crm:business:query')")
114 144 public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) {
115 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 149 @GetMapping("/page-by-customer")
... ... @@ -123,7 +153,7 @@ public class CrmBusinessController {
123 153 throw exception(CUSTOMER_NOT_EXISTS);
124 154 }
125 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 159 @GetMapping("/page-by-contact")
... ... @@ -131,7 +161,7 @@ public class CrmBusinessController {
131 161 @PreAuthorize("@ss.hasPermission('crm:business:query')")
132 162 public CommonResult<PageResult<CrmBusinessRespVO>> getBusinessContactPage(@Valid CrmBusinessPageReqVO pageReqVO) {
133 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 167 @GetMapping("/export-excel")
... ... @@ -141,29 +171,44 @@ public class CrmBusinessController {
141 171 public void exportBusinessExcel(@Valid CrmBusinessPageReqVO exportReqVO,
142 172 HttpServletResponse response) throws IOException {
143 173 exportReqVO.setPageSize(PAGE_SIZE_NONE);
144   - PageResult<CrmBusinessDO> pageResult = businessService.getBusinessPage(exportReqVO, getLoginUserId());
  174 + List<CrmBusinessDO> list = businessService.getBusinessPage(exportReqVO, getLoginUserId()).getList();
145 175 // 导出 Excel
146 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 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 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 5 import io.swagger.v3.oas.annotations.media.Schema;
4   -import jakarta.validation.constraints.NotNull;
  6 +import lombok.AllArgsConstructor;
5 7 import lombok.Data;
6   -import org.springframework.format.annotation.DateTimeFormat;
  8 +import lombok.NoArgsConstructor;
7 9  
8 10 import java.math.BigDecimal;
9 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 15 @Data
  16 +@ExcelIgnoreUnannotated
15 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 21 private Long id;
19 22  
20 23 @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
21   - @NotNull(message = "商机名称不能为空")
  24 + @ExcelProperty("商机名称")
22 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 56 private Long statusTypeId;
  57 + @Schema(description = "商机状组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中")
  58 + @ExcelProperty("商机状态组")
  59 + private String statusTypeName;
27 60  
28 61 @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320")
29   - @NotNull(message = "商机状态不能为空")
30 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 74 @Schema(description = "预计成交日期")
41   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  75 + @ExcelProperty("预计成交日期")
42 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 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 90 @Schema(description = "备注", example = "随便")
  91 + @ExcelProperty("备注")
55 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 101 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  102 + @ExcelProperty("创建时间")
58 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 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 3 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction;
  4 +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFunction;
6 5 import com.mzt.logapi.starter.annotation.DiffLogField;
7 6 import io.swagger.v3.oas.annotations.media.Schema;
8 7 import jakarta.validation.constraints.NotNull;
... ... @@ -29,75 +28,68 @@ public class CrmBusinessSaveReqVO {
29 28 @NotNull(message = "商机名称不能为空")
30 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 36 @Schema(description = "下次联系时间")
43 37 @DiffLogField(name = "下次联系时间")
44 38 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
45 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 51 @Schema(description = "预计成交日期")
53 52 @DiffLogField(name = "预计成交日期")
54 53 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
55 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 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 61 @Schema(description = "备注", example = "随便")
70 62 @DiffLogField(name = "备注")
71 63 private String remark;
72 64  
73   - @Schema(description = "结束状态", example = "1")
74   - @InEnum(CrmBizEndStatus.class)
75   - private Integer endStatus;
76   -
77 65 @Schema(description = "联系人编号", example = "110")
78 66 private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段
79 67  
80   - // TODO @puhui999:传递 items 就行啦;
81 68 @Schema(description = "产品列表")
82   - private List<CrmBusinessProductItem> productItems;
  69 + private List<Product> products;
83 70  
84 71 @Schema(description = "产品列表")
85 72 @Data
86 73 @NoArgsConstructor
87 74 @AllArgsConstructor
88   - public static class CrmBusinessProductItem {
  75 + public static class Product {
89 76  
90 77 @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
91 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 89 @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
95 90 @NotNull(message = "产品数量不能为空")
96 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 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 3 import io.swagger.v3.oas.annotations.media.Schema;
6 4 import lombok.Data;
7 5  
  6 +import java.math.BigDecimal;
  7 +import java.time.LocalDateTime;
  8 +import java.util.List;
  9 +
8 10 @Schema(description = "管理后台 - 商机状态 Response VO")
9 11 @Data
10   -@ExcelIgnoreUnannotated
11 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 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 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 1 package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.Data;
5   -
  4 +import jakarta.validation.Valid;
6 5 import jakarta.validation.constraints.NotEmpty;
7 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 13 @Data
11 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 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 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 1 package cn.iocoder.yudao.module.crm.controller.admin.clue;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 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 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  10 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
7 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 16 import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO;
  17 +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
10 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 24 import io.swagger.v3.oas.annotations.Operation;
12 25 import io.swagger.v3.oas.annotations.Parameter;
13 26 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -19,12 +32,18 @@ import org.springframework.validation.annotation.Validated;
19 32 import org.springframework.web.bind.annotation.*;
20 33  
21 34 import java.io.IOException;
  35 +import java.util.Collections;
22 36 import java.util.List;
  37 +import java.util.Map;
  38 +import java.util.stream.Stream;
23 39  
24 40 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
25 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 44 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
27 45 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  46 +import static java.util.Collections.singletonList;
28 47  
29 48 @Tag(name = "管理后台 - 线索")
30 49 @RestController
... ... @@ -34,12 +53,19 @@ public class CrmClueController {
34 53  
35 54 @Resource
36 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 64 @PostMapping("/create")
39 65 @Operation(summary = "创建线索")
40 66 @PreAuthorize("@ss.hasPermission('crm:clue:create')")
41 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 71 @PutMapping("/update")
... ... @@ -65,7 +91,14 @@ public class CrmClueController {
65 91 @PreAuthorize("@ss.hasPermission('crm:clue:query')")
66 92 public CommonResult<CrmClueRespVO> getClue(@RequestParam("id") Long id) {
67 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 104 @GetMapping("/page")
... ... @@ -73,7 +106,7 @@ public class CrmClueController {
73 106 @PreAuthorize("@ss.hasPermission('crm:clue:query')")
74 107 public CommonResult<PageResult<CrmClueRespVO>> getCluePage(@Valid CrmCluePageReqVO pageVO) {
75 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 112 @GetMapping("/export-excel")
... ... @@ -84,8 +117,33 @@ public class CrmClueController {
84 117 pageReqVO.setPageSize(PAGE_SIZE_NONE);
85 118 List<CrmClueDO> list = clueService.getCluePage(pageReqVO, getLoginUserId()).getList();
86 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 149 @PutMapping("/transfer")
... ... @@ -96,12 +154,20 @@ public class CrmClueController {
96 154 return success(true);
97 155 }
98 156  
99   - @PostMapping("/transform")
  157 + @PutMapping("/transform")
100 158 @Operation(summary = "线索转化为客户")
  159 + @Parameter(name = "id", description = "编号", required = true)
101 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 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 17 @Schema(description = "线索名称", example = "线索xxx")
18 18 private String name;
19 19  
  20 + @Schema(description = "转化状态", example = "2048")
  21 + private Boolean transformStatus;
  22 +
20 23 @Schema(description = "电话", example = "18000000000")
21 24 private String telephone;
22 25  
... ... @@ -39,4 +42,7 @@ public class CrmCluePageReqVO extends PageParam {
39 42 @Schema(description = "客户来源", example = "1")
40 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 8 import io.swagger.v3.oas.annotations.media.Schema;
9 9 import lombok.Data;
10 10 import lombok.ToString;
11   -import org.springframework.format.annotation.DateTimeFormat;
12 11  
13 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 14 @Schema(description = "管理后台 - 线索 Response VO")
18 15 @Data
19 16 @ToString(callSuper = true)
... ... @@ -24,58 +21,76 @@ public class CrmClueRespVO {
24 21 @ExcelProperty("编号")
25 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 28 @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
33 29 @ExcelProperty(value = "跟进状态", converter = DictConvert.class)
34 30 @DictFormat(DictTypeConstants.BOOLEAN_STRING)
35 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 41 @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
47   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
48 42 @ExcelProperty("下次联系时间")
49 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 65 @Schema(description = "手机号", example = "18000000000")
56 66 @ExcelProperty("手机号")
57 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 95 @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
81 96 @ExcelProperty(value = "所属行业", converter = DictConvert.class)
... ... @@ -92,24 +107,23 @@ public class CrmClueRespVO {
92 107 @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE)
93 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 8 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerIndustryParseFunction;
9 9 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerLevelParseFunction;
10 10 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerSourceParseFunction;
  11 +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAreaParseFunction;
11 12 import com.mzt.logapi.starter.annotation.DiffLogField;
12 13 import io.swagger.v3.oas.annotations.media.Schema;
13 14 import jakarta.validation.constraints.Email;
14 15 import jakarta.validation.constraints.NotEmpty;
  16 +import jakarta.validation.constraints.NotNull;
15 17 import jakarta.validation.constraints.Size;
16 18 import lombok.Data;
17 19 import org.springframework.format.annotation.DateTimeFormat;
... ... @@ -21,7 +23,7 @@ import java.time.LocalDateTime;
21 23 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
22 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 27 @Data
26 28 public class CrmClueSaveReqVO {
27 29  
... ... @@ -33,36 +35,53 @@ public class CrmClueSaveReqVO {
33 35 @NotEmpty(message = "线索名称不能为空")
34 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 43 @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00")
37 44 @DiffLogField(name = "下次联系时间")
38 45 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
39 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 52 @Schema(description = "手机号", example = "18000000000")
47 53 @DiffLogField(name = "手机号")
48 54 @Mobile
49 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 86 @Schema(description = "所属行业", example = "1")
68 87 @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME)
... ... @@ -78,28 +97,13 @@ public class CrmClueSaveReqVO {
78 97 @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME)
79 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 100 @Schema(description = "客户描述", example = "任意文字")
102 101 @DiffLogField(name = "客户描述")
103 102 @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符")
104 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 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5   -import cn.hutool.core.util.NumberUtil;
6 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
8 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
9 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  11 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
11 12 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
12 13 import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*;
13   -import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert;
14 14 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
15 15 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
16   -import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants;
17 16 import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService;
18 17 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
19 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 21 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
21 22 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
22   -import com.google.common.collect.Lists;
23 23 import io.swagger.v3.oas.annotations.Operation;
24 24 import io.swagger.v3.oas.annotations.Parameter;
25 25 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -37,12 +37,13 @@ import java.util.List;
37 37 import java.util.Map;
38 38 import java.util.stream.Stream;
39 39  
40   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
41 40 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
42 41 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
43 42 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  43 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
44 44 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
45 45 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  46 +import static java.util.Collections.singletonList;
46 47  
47 48 @Tag(name = "管理后台 - CRM 联系人")
48 49 @RestController
... ... @@ -60,6 +61,8 @@ public class CrmContactController {
60 61  
61 62 @Resource
62 63 private AdminUserApi adminUserApi;
  64 + @Resource
  65 + private DeptApi deptApi;
63 66  
64 67 @PostMapping("/create")
65 68 @Operation(summary = "创建联系人")
... ... @@ -92,36 +95,24 @@ public class CrmContactController {
92 95 @PreAuthorize("@ss.hasPermission('crm:contact:query')")
93 96 public CommonResult<CrmContactRespVO> getContact(@RequestParam("id") Long id) {
94 97 CrmContactDO contact = contactService.getContact(id);
  98 + return success(buildContactDetail(contact));
  99 + }
  100 +
  101 + private CrmContactRespVO buildContactDetail(CrmContactDO contact) {
95 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 108 @GetMapping("/simple-all-list")
119 109 @Operation(summary = "获得联系人的精简列表")
120 110 @PreAuthorize("@ss.hasPermission('crm:contact:query')")
121 111 public CommonResult<List<CrmContactRespVO>> getSimpleContactList() {
122   - List<CrmContactDO> list = contactService.getSimpleContactList(getLoginUserId());
  112 + List<CrmContactDO> list = contactService.getContactList(getLoginUserId());
123 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 118 @GetMapping("/page")
... ... @@ -129,7 +120,7 @@ public class CrmContactController {
129 120 @PreAuthorize("@ss.hasPermission('crm:contact:query')")
130 121 public CommonResult<PageResult<CrmContactRespVO>> getContactPage(@Valid CrmContactPageReqVO pageVO) {
131 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 126 @GetMapping("/page-by-customer")
... ... @@ -137,7 +128,15 @@ public class CrmContactController {
137 128 public CommonResult<PageResult<CrmContactRespVO>> getContactPageByCustomer(@Valid CrmContactPageReqVO pageVO) {
138 129 Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空");
139 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 142 @GetMapping("/export-excel")
... ... @@ -147,32 +146,39 @@ public class CrmContactController {
147 146 public void exportContactExcel(@Valid CrmContactPageReqVO exportReqVO,
148 147 HttpServletResponse response) throws IOException {
149 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 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 159 convertSet(contactList, CrmContactDO::getCustomerId));
169   - // 2. 获取创建人、负责人列表
  160 + // 1.2 获取创建人、负责人列表
170 161 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList,
171 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 184 @PutMapping("/transfer")
... ... @@ -183,7 +189,7 @@ public class CrmContactController {
183 189 return success(true);
184 190 }
185 191  
186   - // ================== 关联/取关联系人 ===================
  192 + // ================== 关联/取关商机 ===================
187 193  
188 194 @PostMapping("/create-business-list")
189 195 @Operation(summary = "创建联系人与商机的关联")
... ... @@ -193,6 +199,15 @@ public class CrmContactController {
193 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 211 @DeleteMapping("/delete-business-list")
197 212 @Operation(summary = "删除联系人与联系人的关联")
198 213 @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')")
... ... @@ -201,4 +216,12 @@ public class CrmContactController {
201 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 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  
8 8 import java.util.List;
9 9  
10   -@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 用于关联,取消关联的操作
  10 +@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 【联系人关联商机】用于关联,取消关联的操作
11 11 @Data
12 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 39 @InEnum(CrmSceneTypeEnum.class)
40 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 20 @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167")
21 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 25 private String name;
26 26  
27 27 @Schema(description = "客户编号", example = "10795")
28 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 54 @Schema(description = "手机号", example = "1387171766")
48 55 @ExcelProperty(value = "手机号", order = 4)
... ... @@ -52,6 +59,10 @@ public class CrmContactRespVO {
52 59 @ExcelProperty(value = "电话", order = 4)
53 60 private String telephone;
54 61  
  62 + @Schema(description = "电子邮箱", example = "1111@22.com")
  63 + @ExcelProperty(value = "邮箱", order = 4)
  64 + private String email;
  65 +
55 66 @Schema(description = "QQ", example = "197272662")
56 67 @ExcelProperty(value = "QQ", order = 4)
57 68 private Long qq;
... ... @@ -60,53 +71,52 @@ public class CrmContactRespVO {
60 71 @ExcelProperty(value = "微信", order = 4)
61 72 private String wechat;
62 73  
63   - @Schema(description = "电子邮箱", example = "1111@22.com")
64   - @ExcelProperty(value = "邮箱", order = 4)
65   - private String email;
66   -
67 74 @Schema(description = "地区编号", example = "20158")
68 75 private Integer areaId;
  76 + @Schema(description = "地区名", example = "上海上海市浦东新区")
  77 + @ExcelProperty(value = "地区", order = 5)
  78 + private String areaName;
69 79  
70 80 @Schema(description = "地址")
71 81 @ExcelProperty(value = "地址", order = 5)
72 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 108 @Schema(description = "创建人", example = "25682")
90 109 private String creator;
91   -
92 110 @Schema(description = "创建人名字", example = "test")
93 111 @ExcelProperty(value = "创建人", order = 8)
94 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 13 import java.time.LocalDateTime;
14 14  
15 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 17 @Schema(description = "管理后台 - CRM 联系人创建/更新 Request VO")
19 18 @Data
... ... @@ -28,24 +27,19 @@ public class CrmContactSaveReqVO {
28 27 private String name;
29 28  
30 29 @Schema(description = "客户编号", example = "10795")
  30 + @NotNull(message = "客户编号不能为空")
31 31 @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME)
32 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 44 @Schema(description = "手机号", example = "1387171766")
51 45 @Mobile
... ... @@ -78,24 +72,25 @@ public class CrmContactSaveReqVO {
78 72 @DiffLogField(name = "地址")
79 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 95 @Schema(description = "关联商机 ID", example = "122233")
101 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 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
6 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 9 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
8 10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 11 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
10 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 17 import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO;
17 18 import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO;
18 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 25 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
25 26 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
26 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 31 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
28 32 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
29 33 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -37,6 +41,7 @@ import org.springframework.validation.annotation.Validated;
37 41 import org.springframework.web.bind.annotation.*;
38 42  
39 43 import java.io.IOException;
  44 +import java.math.BigDecimal;
40 45 import java.util.Collections;
41 46 import java.util.List;
42 47 import java.util.Map;
... ... @@ -44,6 +49,7 @@ import java.util.stream.Stream;
44 49  
45 50 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
46 51 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  52 +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
47 53 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
48 54 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
49 55 import static java.util.Collections.singletonList;
... ... @@ -65,7 +71,12 @@ public class CrmContractController {
65 71 @Resource
66 72 private CrmProductService productService;
67 73 @Resource
  74 + private CrmReceivableService receivableService;
  75 +
  76 + @Resource
68 77 private AdminUserApi adminUserApi;
  78 + @Resource
  79 + private DeptApi deptApi;
69 80  
70 81 @PostMapping("/create")
71 82 @Operation(summary = "创建合同")
... ... @@ -96,15 +107,24 @@ public class CrmContractController {
96 107 @Parameter(name = "id", description = "编号", required = true, example = "1024")
97 108 @PreAuthorize("@ss.hasPermission('crm:contract:query')")
98 109 public CommonResult<CrmContractRespVO> getContract(@RequestParam("id") Long id) {
99   - // 1. 查询合同
100 110 CrmContractDO contract = contractService.getContract(id);
  111 + return success(buildContractDetail(contract));
  112 + }
  113 +
  114 + private CrmContractRespVO buildContractDetail(CrmContractDO contract) {
101 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 130 @GetMapping("/page")
... ... @@ -123,6 +143,14 @@ public class CrmContractController {
123 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 154 @GetMapping("/export-excel")
127 155 @Operation(summary = "导出合同 Excel")
128 156 @PreAuthorize("@ss.hasPermission('crm:contract:export')")
... ... @@ -151,37 +179,78 @@ public class CrmContractController {
151 179 return success(true);
152 180 }
153 181  
154   - /**
155   - * 构建详细的合同结果
156   - *
157   - * @param contractList 原始合同信息
158   - * @return 细的合同结果
159   - */
160 182 private List<CrmContractRespVO> buildContractDetailList(List<CrmContractDO> contractList) {
161 183 if (CollUtil.isEmpty(contractList)) {
162 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 188 convertSet(contractList, CrmContractDO::getCustomerId));
167   - // 2. 获取创建人、负责人列表
  189 + // 1.2 获取创建人、负责人列表
168 190 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(contractList,
169 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 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 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 3 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
4 4 import com.alibaba.excel.annotation.ExcelProperty;
... ... @@ -6,13 +6,11 @@ import io.swagger.v3.oas.annotations.media.Schema;
6 6 import lombok.AllArgsConstructor;
7 7 import lombok.Data;
8 8 import lombok.NoArgsConstructor;
9   -import org.springframework.format.annotation.DateTimeFormat;
10 9  
  10 +import java.math.BigDecimal;
11 11 import java.time.LocalDateTime;
12 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 14 @Schema(description = "管理后台 - CRM 合同 Response VO")
17 15 @Data
18 16 @ExcelIgnoreUnannotated
... ... @@ -26,6 +24,10 @@ public class CrmContractRespVO {
26 24 @ExcelProperty("合同名称")
27 25 private String name;
28 26  
  27 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101")
  28 + @ExcelProperty("合同编号")
  29 + private String no;
  30 +
29 31 @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336")
30 32 @ExcelProperty("客户编号")
31 33 private Long customerId;
... ... @@ -40,72 +42,74 @@ public class CrmContractRespVO {
40 42 @ExcelProperty("商机名称")
41 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 59 @Schema(description = "工作流编号", example = "1043")
44 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 67 @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED)
48 68 @ExcelProperty("下单日期")
49   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
50 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 71 @Schema(description = "开始时间")
62 72 @ExcelProperty("开始时间")
63   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
64 73 private LocalDateTime startTime;
65 74  
66 75 @Schema(description = "结束时间")
67 76 @ExcelProperty("结束时间")
68   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
69 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 83 @Schema(description = "整单折扣")
76 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 101 @Schema(description = "公司签约人", example = "14036")
91   - @ExcelProperty("公司签约人")
92 102 private Long signUserId;
93   - @Schema(description = "公司签约人", example = "14036")
  103 + @Schema(description = "公司签约人", example = "小明")
94 104 @ExcelProperty("公司签约人")
95 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 107 @Schema(description = "备注", example = "你猜")
103 108 @ExcelProperty("备注")
104 109 private String remark;
105 110  
106 111 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
107 112 @ExcelProperty("创建时间")
108   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
109 113 private LocalDateTime createTime;
110 114  
111 115 @Schema(description = "创建人", example = "25682")
... ... @@ -118,47 +122,40 @@ public class CrmContractRespVO {
118 122  
119 123 @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
120 124 @ExcelProperty("更新时间")
121   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
122 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 127 @Schema(description = "产品列表")
133   - private List<CrmContractProductItemRespVO> productItems;
  128 + private List<Product> products;
134 129  
135   - // TODO @puhui999:可以直接叫 Item
136 130 @Schema(description = "产品列表")
137 131 @Data
138 132 @NoArgsConstructor
139 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 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 3 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmBusinessParseFunction;
4 4 import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmContactParseFunction;
... ... @@ -12,6 +12,7 @@ import lombok.Data;
12 12 import lombok.NoArgsConstructor;
13 13 import org.springframework.format.annotation.DateTimeFormat;
14 14  
  15 +import java.math.BigDecimal;
15 16 import java.time.LocalDateTime;
16 17 import java.util.List;
17 18  
... ... @@ -38,22 +39,16 @@ public class CrmContractSaveReqVO {
38 39 @DiffLogField(name = "商机", function = CrmBusinessParseFunction.NAME)
39 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 42 @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144")
48 43 @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME)
49 44 @NotNull(message = "负责人不能为空")
50 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 53 @Schema(description = "开始时间")
59 54 @DiffLogField(name = "开始时间")
... ... @@ -65,21 +60,18 @@ public class CrmContractSaveReqVO {
65 60 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
66 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 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 76 @Schema(description = "公司签约人", example = "14036")
85 77 @DiffLogField(name = "公司签约人", function = SysAdminUserParseFunction.NAME)
... ... @@ -89,27 +81,31 @@ public class CrmContractSaveReqVO {
89 81 @DiffLogField(name = "备注")
90 82 private String remark;
91 83  
92   -
93 84 @Schema(description = "产品列表")
94   - private List<CrmContractProductItem> productItems;
  85 + private List<Product> products;
95 86  
96 87 @Schema(description = "产品列表")
97 88 @Data
98 89 @NoArgsConstructor
99 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 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 105 @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
107 106 @NotNull(message = "产品数量不能为空")
108 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 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 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 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.map.MapUtil;
5   -import cn.hutool.core.util.ObjUtil;
  5 +import cn.iocoder.yudao.framework.common.core.KeyValue;
6 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
7 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 10 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
  11 +import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
9 12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  13 +import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn;
10 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 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 19 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
15 20 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO;
16 21 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService;
17 22 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
18 23 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
19 24 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  25 +import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
20 26 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
21 27 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
22 28 import io.swagger.v3.oas.annotations.Operation;
23 29 import io.swagger.v3.oas.annotations.Parameter;
  30 +import io.swagger.v3.oas.annotations.Parameters;
24 31 import io.swagger.v3.oas.annotations.tags.Tag;
25 32 import jakarta.annotation.Resource;
26 33 import jakarta.servlet.http.HttpServletResponse;
27 34 import jakarta.validation.Valid;
28   -import org.mapstruct.ap.internal.util.Collections;
29 35 import org.springframework.security.access.prepost.PreAuthorize;
30 36 import org.springframework.validation.annotation.Validated;
31 37 import org.springframework.web.bind.annotation.*;
32 38  
33 39 import java.io.IOException;
34 40 import java.time.LocalDateTime;
  41 +import java.util.ArrayList;
35 42 import java.util.Arrays;
36 43 import java.util.List;
37 44 import java.util.Map;
38 45 import java.util.stream.Stream;
39 46  
40   -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
41 47 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
42 48 import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
43 49 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
44 50 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
45 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 55 @Tag(name = "管理后台 - CRM 客户")
49 56 @RestController
... ... @@ -55,10 +62,13 @@ public class CrmCustomerController {
55 62 private CrmCustomerService customerService;
56 63 @Resource
57 64 private CrmCustomerPoolConfigService customerPoolConfigService;
  65 +
58 66 @Resource
59 67 private DeptApi deptApi;
60 68 @Resource
61 69 private AdminUserApi adminUserApi;
  70 + @Resource
  71 + private DictDataApi dictDataApi;
62 72  
63 73 @PostMapping("/create")
64 74 @Operation(summary = "创建客户")
... ... @@ -75,6 +85,18 @@ public class CrmCustomerController {
75 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 100 @DeleteMapping("/delete")
79 101 @Operation(summary = "删除客户")
80 102 @Parameter(name = "id", description = "客户编号", required = true)
... ... @@ -91,94 +113,126 @@ public class CrmCustomerController {
91 113 public CommonResult<CrmCustomerRespVO> getCustomer(@RequestParam("id") Long id) {
92 114 // 1. 获取客户
93 115 CrmCustomerDO customer = customerService.getCustomer(id);
  116 + // 2. 拼接数据
  117 + return success(buildCustomerDetail(customer));
  118 + }
  119 +
  120 + public CrmCustomerRespVO buildCustomerDetail(CrmCustomerDO customer) {
94 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 127 @GetMapping("/page")
105 128 @Operation(summary = "获得客户分页")
106 129 @PreAuthorize("@ss.hasPermission('crm:customer:query')")
107 130 public CommonResult<PageResult<CrmCustomerRespVO>> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) {
  131 + customerService.autoPutCustomerPool();
108 132 // 1. 查询客户分页
109 133 PageResult<CrmCustomerDO> pageResult = customerService.getCustomerPage(pageVO, getLoginUserId());
110 134 if (CollUtil.isEmpty(pageResult.getList())) {
111 135 return success(PageResult.empty(pageResult.getTotal()));
112 136 }
113   -
114 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 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 169 @Operation(summary = "获得待进入公海客户分页")
125 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 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 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 206 CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
159 207 if (poolConfig == null || !poolConfig.getEnabled()) {
160 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 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 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 234 @Operation(summary = "获取客户精简信息列表", description = "只包含有读权限的客户,主要用于前端的下拉选项")
181   - public CommonResult<List<CrmCustomerRespVO>> getSimpleDeptList() {
  235 + public CommonResult<List<CrmCustomerRespVO>> getCustomerSimpleList() {
182 236 CrmCustomerPageReqVO reqVO = new CrmCustomerPageReqVO();
183 237 reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页
184 238 List<CrmCustomerDO> list = customerService.getCustomerPage(reqVO, getLoginUserId()).getList();
... ... @@ -186,7 +240,6 @@ public class CrmCustomerController {
186 240 new CrmCustomerRespVO().setId(customer.getId()).setName(customer.getName())));
187 241 }
188 242  
189   - // TODO @puhui999:公海的导出,前端可以接下
190 243 @GetMapping("/export-excel")
191 244 @Operation(summary = "导出客户 Excel")
192 245 @PreAuthorize("@ss.hasPermission('crm:customer:export')")
... ... @@ -197,7 +250,7 @@ public class CrmCustomerController {
197 250 List<CrmCustomerDO> list = customerService.getCustomerPage(pageVO, getLoginUserId()).getList();
198 251 // 导出 Excel
199 252 ExcelUtils.write(response, "客户.xls", "数据", CrmCustomerRespVO.class,
200   - BeanUtils.toBean(list, CrmCustomerRespVO.class));
  253 + buildCustomerDetailList(list));
201 254 }
202 255  
203 256 @GetMapping("/get-import-template")
... ... @@ -205,15 +258,33 @@ public class CrmCustomerController {
205 258 public void importTemplate(HttpServletResponse response) throws IOException {
206 259 // 手动创建导出 demo
207 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 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 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 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 8 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO;
7 9 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigRespVO;
8 10 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO;
9   -import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerLimitConfigConvert;
10 11 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
11 12 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerLimitConfigService;
12 13 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
... ... @@ -71,11 +72,14 @@ public class CrmCustomerLimitConfigController {
71 72 @Parameter(name = "id", description = "编号", required = true, example = "1024")
72 73 @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:query')")
73 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 85 @GetMapping("/page")
... ... @@ -91,7 +95,10 @@ public class CrmCustomerLimitConfigController {
91 95 convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getUserIds, Collection::stream));
92 96 Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
93 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 3 import io.swagger.v3.oas.annotations.media.Schema;
4 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 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
4 5 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 6 import com.alibaba.excel.annotation.ExcelProperty;
6 7 import lombok.AllArgsConstructor;
... ... @@ -24,28 +25,12 @@ public class CrmCustomerImportExcelVO {
24 25 @ExcelProperty("客户名称")
25 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 28 @ExcelProperty("手机")
41 29 private String mobile;
42 30  
43 31 @ExcelProperty("电话")
44 32 private String telephone;
45 33  
46   - @ExcelProperty("网址")
47   - private String website;
48   -
49 34 @ExcelProperty("QQ")
50 35 private String qq;
51 36  
... ... @@ -55,17 +40,25 @@ public class CrmCustomerImportExcelVO {
55 40 @ExcelProperty("邮箱")
56 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 44 private Integer areaId;
67 45  
68 46 @ExcelProperty("详细地址")
69 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 3 import io.swagger.v3.oas.annotations.media.Schema;
4 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 3 import io.swagger.v3.oas.annotations.media.Schema;
4 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 3 import io.swagger.v3.oas.annotations.media.Schema;
4 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 3 import cn.iocoder.yudao.framework.common.pojo.PageParam;
4 4 import cn.iocoder.yudao.framework.common.validation.InEnum;
... ... @@ -14,6 +14,19 @@ import lombok.ToString;
14 14 @ToString(callSuper = true)
15 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 30 @Schema(description = "客户名称", example = "赵六")
18 31 private String name;
19 32  
... ... @@ -36,4 +49,10 @@ public class CrmCustomerPageReqVO extends PageParam {
36 49 @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
37 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 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
4 4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
... ... @@ -7,12 +7,9 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
7 7 import com.alibaba.excel.annotation.ExcelProperty;
8 8 import io.swagger.v3.oas.annotations.media.Schema;
9 9 import lombok.Data;
10   -import org.springframework.format.annotation.DateTimeFormat;
11 10  
12 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 13 @Schema(description = "管理后台 - CRM 客户 Response VO")
17 14 @Data
18 15 @ExcelIgnoreUnannotated
... ... @@ -31,6 +28,28 @@ public class CrmCustomerRespVO {
31 28 @DictFormat(DictTypeConstants.BOOLEAN_STRING)
32 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 53 @Schema(description = "锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
35 54 @ExcelProperty(value = "锁定状态", converter = DictConvert.class)
36 55 @DictFormat(DictTypeConstants.BOOLEAN_STRING)
... ... @@ -41,63 +60,26 @@ public class CrmCustomerRespVO {
41 60 @DictFormat(DictTypeConstants.BOOLEAN_STRING)
42 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 64 @ExcelProperty("手机")
61 65 private String mobile;
62 66  
63   - @Schema(description = "负责人的用户编号", example = "25682")
  67 + @Schema(description = "电话", example = "25682")
64 68 @ExcelProperty("电话")
65 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 72 @ExcelProperty("QQ")
73 73 private String qq;
74 74  
75   - @Schema(description = "负责人的用户编号", example = "25682")
  75 + @Schema(description = "wechat", example = "25682")
76 76 @ExcelProperty("wechat")
77 77 private String wechat;
78 78  
79   - @Schema(description = "负责人的用户编号", example = "25682")
  79 + @Schema(description = "email", example = "25682")
80 80 @ExcelProperty("email")
81 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 83 @Schema(description = "地区编号", example = "1024")
102 84 @ExcelProperty("地区编号")
103 85 private Integer areaId;
... ... @@ -108,14 +90,24 @@ public class CrmCustomerRespVO {
108 90 @ExcelProperty("详细地址")
109 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 112 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
121 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 3 import cn.iocoder.yudao.framework.common.validation.InEnum;
4 4 import cn.iocoder.yudao.framework.common.validation.Mobile;
... ... @@ -13,6 +13,7 @@ import com.mzt.logapi.starter.annotation.DiffLogField;
13 13 import io.swagger.v3.oas.annotations.media.Schema;
14 14 import jakarta.validation.constraints.Email;
15 15 import jakarta.validation.constraints.NotEmpty;
  16 +import jakarta.validation.constraints.NotNull;
16 17 import jakarta.validation.constraints.Size;
17 18 import lombok.Data;
18 19 import org.springframework.format.annotation.DateTimeFormat;
... ... @@ -34,19 +35,14 @@ public class CrmCustomerSaveReqVO {
34 35 @NotEmpty(message = "客户名称不能为空")
35 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 47 @Schema(description = "手机", example = "18000000000")
52 48 @DiffLogField(name = "手机")
... ... @@ -58,10 +54,6 @@ public class CrmCustomerSaveReqVO {
58 54 @Telephone
59 55 private String telephone;
60 56  
61   - @Schema(description = "网址", example = "https://www.baidu.com")
62   - @DiffLogField(name = "网址")
63   - private String website;
64   -
65 57 @Schema(description = "QQ", example = "123456789")
66 58 @DiffLogField(name = "QQ")
67 59 @Size(max = 20, message = "QQ长度不能超过 20 个字符")
... ... @@ -78,15 +70,6 @@ public class CrmCustomerSaveReqVO {
78 70 @Size(max = 255, message = "邮箱长度不能超过 255 个字符")
79 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 73 @Schema(description = "地区编号", example = "20158")
91 74 @DiffLogField(name = "地区编号", function = SysAreaParseFunction.NAME)
92 75 private Integer areaId;
... ... @@ -95,12 +78,22 @@ public class CrmCustomerSaveReqVO {
95 78 @DiffLogField(name = "详细地址")
96 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 3 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
4 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 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
6 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  7 +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
7 8 import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO;
8 9 import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordRespVO;
9 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 14 import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
14 15 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
15 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 19 import io.swagger.v3.oas.annotations.Operation;
17 20 import io.swagger.v3.oas.annotations.Parameter;
18 21 import io.swagger.v3.oas.annotations.tags.Tag;
... ... @@ -26,7 +29,7 @@ import java.util.ArrayList;
26 29 import java.util.Map;
27 30  
28 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 33 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
31 34 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
32 35  
... ... @@ -44,6 +47,9 @@ public class CrmFollowUpRecordController {
44 47 @Resource
45 48 private CrmBusinessService businessService;
46 49  
  50 + @Resource
  51 + private AdminUserApi adminUserApi;
  52 +
47 53 @PostMapping("/create")
48 54 @Operation(summary = "创建跟进记录")
49 55 @PreAuthorize("@ss.hasPermission('crm:follow-up-record:create')")
... ... @@ -74,17 +80,24 @@ public class CrmFollowUpRecordController {
74 80 @PreAuthorize("@ss.hasPermission('crm:follow-up-record:query')")
75 81 public CommonResult<PageResult<CrmFollowUpRecordRespVO>> getFollowUpRecordPage(@Valid CrmFollowUpRecordPageReqVO pageReqVO) {
76 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 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 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 1 package cn.iocoder.yudao.module.crm.controller.admin.followup.vo;
2 2  
3 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
4 5 import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
  6 +import com.alibaba.excel.annotation.ExcelProperty;
5 7 import io.swagger.v3.oas.annotations.media.Schema;
6 8 import lombok.Data;
7 9  
... ... @@ -36,19 +38,26 @@ public class CrmFollowUpRecordRespVO {
36 38  
37 39 @Schema(description = "关联的商机编号数组")
38 40 private List<Long> businessIds;
39   - @Schema(description = "关联的商机名称数组")
40   - private List<String> businessNames;
  41 + @Schema(description = "关联的商机数组")
  42 + private List<CrmBusinessRespVO> businesses;
41 43  
42 44 @Schema(description = "关联的联系人编号数组")
43 45 private List<Long> contactIds;
44 46 @Schema(description = "关联的联系人名称数组")
45   - private List<String> contactNames;
  47 + private List<CrmBusinessRespVO> contacts;
46 48  
47 49 @Schema(description = "图片")
48 50 private List<String> picUrls;
49 51 @Schema(description = "附件")
50 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 61 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
53 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 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 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 8 import cn.iocoder.yudao.module.crm.enums.LogRecordConstants;
9 9 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
10 10 import cn.iocoder.yudao.module.system.api.logger.OperateLogApi;
... ... @@ -41,7 +41,7 @@ public class CrmOperateLogController {
41 41 private static final Map<Integer, String> BIZ_TYPE_MAP = new HashMap<>();
42 42  
43 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 45 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CUSTOMER.getType(), CRM_CUSTOMER_TYPE);
46 46 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CONTACT.getType(), CRM_CONTACT_TYPE);
47 47 BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_BUSINESS.getType(), CRM_BUSINESS_TYPE);
... ... @@ -54,11 +54,11 @@ public class CrmOperateLogController {
54 54 @GetMapping("/page")
55 55 @Operation(summary = "获得操作日志")
56 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 58 OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO();
59 59 reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页,需要分页需注释
60 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 6  
7 7 import java.time.LocalDateTime;
8 8  
9   -@Schema(description = "管理后台 - CRM 跟进 Response VO")
  9 +@Schema(description = "管理后台 - CRM 操作日志 Response VO")
10 10 @Data
11 11 @ExcelIgnoreUnannotated
12   -public class CrmOperateLogV2RespVO {
  12 +public class CrmOperateLogRespVO {
13 13  
14 14 @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563")
15 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 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 5 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  6 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
6 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 8 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO;
8 9 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO;
9 10 import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO;
10   -import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert;
11 11 import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
12 12 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
13 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 19 import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
20 20 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
21 21 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
  22 +import com.google.common.collect.Multimaps;
22 23 import io.swagger.v3.oas.annotations.Operation;
23 24 import io.swagger.v3.oas.annotations.Parameter;
24 25 import io.swagger.v3.oas.annotations.Parameters;
... ... @@ -29,11 +30,16 @@ import org.springframework.security.access.prepost.PreAuthorize;
29 30 import org.springframework.validation.annotation.Validated;
30 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 37 import java.util.stream.Stream;
34 38  
35 39 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
36 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 43 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
38 44  
39 45 @Tag(name = "管理后台 - CRM 数据权限")
... ... @@ -98,18 +104,32 @@ public class CrmPermissionController {
98 104 @PreAuthorize("@ss.hasPermission('crm:permission:query')")
99 105 public CommonResult<List<CrmPermissionRespVO>> getPermissionList(@RequestParam("bizType") Integer bizType,
100 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 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 1 package cn.iocoder.yudao.module.crm.controller.admin.product;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageParam;
6 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 10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 11 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10 12 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO;
11 13 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO;
12 14 import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO;
13   -import cn.iocoder.yudao.module.crm.convert.product.CrmProductConvert;
14 15 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO;
15 16 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
16 17 import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService;
... ... @@ -34,10 +35,9 @@ import java.util.Map;
34 35 import java.util.stream.Stream;
35 36  
36 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 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 42 @Tag(name = "管理后台 - CRM 产品")
43 43 @RestController
... ... @@ -49,6 +49,7 @@ public class CrmProductController {
49 49 private CrmProductService productService;
50 50 @Resource
51 51 private CrmProductCategoryService productCategoryService;
  52 +
52 53 @Resource
53 54 private AdminUserApi adminUserApi;
54 55  
... ... @@ -82,21 +83,30 @@ public class CrmProductController {
82 83 @PreAuthorize("@ss.hasPermission('crm:product:query')")
83 84 public CommonResult<CrmProductRespVO> getProduct(@RequestParam("id") Long id) {
84 85 CrmProductDO product = productService.getProduct(id);
  86 + return success(buildProductDetail(product));
  87 + }
  88 +
  89 + private CrmProductRespVO buildProductDetail(CrmProductDO product) {
85 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 104 @GetMapping("/page")
95 105 @Operation(summary = "获得产品分页")
96 106 @PreAuthorize("@ss.hasPermission('crm:product:query')")
97 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 112 @GetMapping("/export-excel")
... ... @@ -106,21 +116,30 @@ public class CrmProductController {
106 116 public void exportProductExcel(@Valid CrmProductPageReqVO exportReqVO,
107 117 HttpServletResponse response) throws IOException {
108 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 120 // 导出 Excel
111 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 126 if (CollUtil.isEmpty(list)) {
117 127 return Collections.emptyList();
118 128 }
  129 + // 1.1 获得用户信息
119 130 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
120 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 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 8 import io.swagger.v3.oas.annotations.media.Schema;
9 9 import lombok.Data;
10 10  
  11 +import java.math.BigDecimal;
11 12 import java.time.LocalDateTime;
12 13  
13 14 @Schema(description = "管理后台 - CRM 产品 Response VO")
... ... @@ -34,7 +35,7 @@ public class CrmProductRespVO {
34 35  
35 36 @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
36 37 @ExcelProperty("价格,单位:分")
37   - private Long price;
  38 + private BigDecimal price;
38 39  
39 40 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架")
40 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 7 import jakarta.validation.constraints.NotNull;
8 8 import lombok.Data;
9 9  
  10 +import java.math.BigDecimal;
  11 +
10 12 @Schema(description = "管理后台 - CRM 产品创建/修改 Request VO")
11 13 @Data
12 14 public class CrmProductSaveReqVO {
... ... @@ -28,10 +30,10 @@ public class CrmProductSaveReqVO {
28 30 @DiffLogField(name = "单位", function = CrmProductUnitParseFunction.NAME)
29 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 34 @NotNull(message = "价格不能为空")
33 35 @DiffLogField(name = "价格")
34   - private Long price;
  36 + private BigDecimal price;
35 37  
36 38 @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架")
37 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 4 import cn.hutool.core.lang.Assert;
5 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
7 8 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  9 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 10 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 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 13 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
12 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 16 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 17 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
17 18 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
18 19 import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
19 20 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
20 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 24 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
22 25 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
23 26 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -31,14 +34,15 @@ import org.springframework.validation.annotation.Validated;
31 34 import org.springframework.web.bind.annotation.*;
32 35  
33 36 import java.io.IOException;
  37 +import java.util.Collections;
34 38 import java.util.List;
35 39 import java.util.Map;
36 40 import java.util.stream.Stream;
37 41  
38 42 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
39 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 46 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
43 47 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
44 48  
... ... @@ -57,18 +61,20 @@ public class CrmReceivableController {
57 61  
58 62 @Resource
59 63 private AdminUserApi adminUserApi;
  64 + @Resource
  65 + private DeptApi deptApi;
60 66  
61 67 @PostMapping("/create")
62 68 @Operation(summary = "创建回款")
63 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 74 @PutMapping("/update")
69 75 @Operation(summary = "更新回款")
70 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 78 receivableService.updateReceivable(updateReqVO);
73 79 return success(true);
74 80 }
... ... @@ -88,7 +94,14 @@ public class CrmReceivableController {
88 94 @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
89 95 public CommonResult<CrmReceivableRespVO> getReceivable(@RequestParam("id") Long id) {
90 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 107 @GetMapping("/page")
... ... @@ -96,7 +109,7 @@ public class CrmReceivableController {
96 109 @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
97 110 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) {
98 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 115 @GetMapping("/page-by-customer")
... ... @@ -104,10 +117,9 @@ public class CrmReceivableController {
104 117 public CommonResult<PageResult<CrmReceivableRespVO>> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) {
105 118 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
106 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 123 @GetMapping("/export-excel")
112 124 @Operation(summary = "导出回款 Excel")
113 125 @PreAuthorize("@ss.hasPermission('crm:receivable:export')")
... ... @@ -115,33 +127,56 @@ public class CrmReceivableController {
115 127 public void exportReceivableExcel(@Valid CrmReceivablePageReqVO exportReqVO,
116 128 HttpServletResponse response) throws IOException {
117 129 exportReqVO.setPageSize(PAGE_SIZE_NONE);
118   - PageResult<CrmReceivableDO> pageResult = receivableService.getReceivablePage(exportReqVO, getLoginUserId());
  130 + List<CrmReceivableDO> list = receivableService.getReceivablePage(exportReqVO, getLoginUserId()).getList();
119 131 // 导出 Excel
120 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 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 142 convertSet(receivableList, CrmReceivableDO::getCustomerId));
138   - // 2. 获取创建人、负责人列表
  143 + // 1.2 获取创建人、负责人列表
139 144 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivableList,
140 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 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 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
11 11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
12 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 15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
17 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 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
25 25 import io.swagger.v3.oas.annotations.Operation;
26 26 import io.swagger.v3.oas.annotations.Parameter;
  27 +import io.swagger.v3.oas.annotations.Parameters;
27 28 import io.swagger.v3.oas.annotations.tags.Tag;
28 29 import jakarta.annotation.Resource;
29 30 import jakarta.servlet.http.HttpServletResponse;
30 31 import jakarta.validation.Valid;
31   -import org.springframework.context.annotation.Lazy;
32 32 import org.springframework.security.access.prepost.PreAuthorize;
33 33 import org.springframework.validation.annotation.Validated;
34 34 import org.springframework.web.bind.annotation.*;
35 35  
36 36 import java.io.IOException;
  37 +import java.util.Collections;
37 38 import java.util.List;
38 39 import java.util.Map;
39 40 import java.util.stream.Stream;
40 41  
41 42 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
42 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 46 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
46 47 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
47 48  
... ... @@ -56,7 +57,6 @@ public class CrmReceivablePlanController {
56 57 @Resource
57 58 private CrmReceivableService receivableService;
58 59 @Resource
59   - @Lazy
60 60 private CrmContractService contractService;
61 61 @Resource
62 62 private CrmCustomerService customerService;
... ... @@ -67,14 +67,14 @@ public class CrmReceivablePlanController {
67 67 @PostMapping("/create")
68 68 @Operation(summary = "创建回款计划")
69 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 74 @PutMapping("/update")
75 75 @Operation(summary = "更新回款计划")
76 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 78 receivablePlanService.updateReceivablePlan(updateReqVO);
79 79 return success(true);
80 80 }
... ... @@ -94,7 +94,14 @@ public class CrmReceivablePlanController {
94 94 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
95 95 public CommonResult<CrmReceivablePlanRespVO> getReceivablePlan(@RequestParam("id") Long id) {
96 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 107 @GetMapping("/page")
... ... @@ -102,7 +109,7 @@ public class CrmReceivablePlanController {
102 109 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
103 110 public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPage(@Valid CrmReceivablePlanPageReqVO pageReqVO) {
104 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 115 @GetMapping("/page-by-customer")
... ... @@ -110,10 +117,9 @@ public class CrmReceivablePlanController {
110 117 public CommonResult<PageResult<CrmReceivablePlanRespVO>> getReceivablePlanPageByCustomer(@Valid CrmReceivablePlanPageReqVO pageReqVO) {
111 118 Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空");
112 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 123 @GetMapping("/export-excel")
118 124 @Operation(summary = "导出回款计划 Excel")
119 125 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:export')")
... ... @@ -121,36 +127,64 @@ public class CrmReceivablePlanController {
121 127 public void exportReceivablePlanExcel(@Valid CrmReceivablePlanPageReqVO exportReqVO,
122 128 HttpServletResponse response) throws IOException {
123 129 exportReqVO.setPageSize(PAGE_SIZE_NONE);
124   - PageResult<CrmReceivablePlanDO> pageResult = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId());
  130 + List<CrmReceivablePlanDO> list = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId()).getList();
125 131 // 导出 Excel
126 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 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 142 convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId));
144   - // 2. 获取创建人、负责人列表
  143 + // 1.2 获取创建人、负责人列表
145 144 Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(receivablePlanList,
146 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 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 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 30 @Schema(description = "客户编号", example = "18026")
31 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 37 private Long contractId;
36 38  
37 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 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 5 import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.*;
  6 +import lombok.Data;
  7 +
  8 +import java.math.BigDecimal;
5 9 import java.time.LocalDateTime;
6 10  
  11 +// TODO @puhui999:缺导出
7 12 @Schema(description = "管理后台 - CRM 回款计划 Response VO")
8 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 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 24 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
20 25 private String customerName;
21 26  
  27 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  28 + private Long contractId;
22 29 @Schema(description = "合同编号", example = "Q110")
23 30 private String contractNo;
24 31  
  32 + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  33 + private Long ownerUserId;
25 34 @Schema(description = "负责人", example = "test")
26 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 24 @Schema(description = "客户编号", example = "4963")
25 25 private Long customerId;
26 26  
  27 + @Schema(description = "合同编号", example = "4963")
  28 + private Long contractId;
  29 +
27 30 @Schema(description = "场景类型", example = "1")
28 31 @InEnum(CrmSceneTypeEnum.class)
29 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 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 5 import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.*;
  6 +import lombok.Data;
  7 +
  8 +import java.math.BigDecimal;
5 9 import java.time.LocalDateTime;
6 10  
7 11 // TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能
8 12 @Schema(description = "管理后台 - CRM 回款 Response VO")
9 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 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 36 @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
21 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 58 @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
24 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 70 @Schema(description = "创建人", example = "25682")
33 71 private String creator;
34   -
35 72 @Schema(description = "创建人名字", example = "test")
36 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 3 Authorization: Bearer {{token}}
4 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 8 Authorization: Bearer {{token}}
9 9 tenant-id: {{adminTenentId}}
10 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 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 7 import io.swagger.v3.oas.annotations.Operation;
8 8 import io.swagger.v3.oas.annotations.tags.Tag;
9 9 import jakarta.annotation.Resource;
... ... @@ -19,68 +19,68 @@ import java.util.List;
19 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 23 @RestController
24   -@RequestMapping("/crm/bi-rank")
  24 +@RequestMapping("/crm/statistics-rank")
25 25 @Validated
26   -public class CrmBiRankController {
  26 +public class CrmStatisticsRankController {
27 27  
28 28 @Resource
29   - private CrmBiRankingService rankingService;
  29 + private CrmStatisticsRankingService rankingService;
30 30  
31 31 @GetMapping("/get-contract-price-rank")
32 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 35 return success(rankingService.getContractPriceRank(rankingReqVO));
36 36 }
37 37  
38 38 @GetMapping("/get-receivable-price-rank")
39 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 42 return success(rankingService.getReceivablePriceRank(rankingReqVO));
43 43 }
44 44  
45 45 @GetMapping("/get-contract-count-rank")
46 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 49 return success(rankingService.getContractCountRank(rankingReqVO));
50 50 }
51 51  
52 52 @GetMapping("/get-product-sales-rank")
53 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 56 return success(rankingService.getProductSalesRank(rankingReqVO));
57 57 }
58 58  
59 59 @GetMapping("/get-customer-count-rank")
60 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 63 return success(rankingService.getCustomerCountRank(rankingReqVO));
64 64 }
65 65  
66 66 @GetMapping("/get-contacts-count-rank")
67 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 70 return success(rankingService.getContactsCountRank(rankingReqVO));
71 71 }
72 72  
73 73 @GetMapping("/get-follow-count-rank")
74 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 77 return success(rankingService.getFollowCountRank(rankingReqVO));
78 78 }
79 79  
80 80 @GetMapping("/get-follow-customer-count-rank")
81 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 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 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.Data;
5 5  
6 6  
7   -@Schema(description = "管理后台 - CRM BI 排行榜 Response VO")
  7 +@Schema(description = "管理后台 - CRM BI 排行榜统计 Response VO")
8 8 @Data
9   -public class CrmBiRanKRespVO {
  9 +public class CrmStatisticsRanKRespVO {
10 10  
11 11 @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
12 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 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import jakarta.validation.constraints.NotEmpty;
... ... @@ -11,9 +11,9 @@ import java.util.List;
11 11  
12 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 15 @Data
16   -public class CrmBiRankReqVO {
  16 +public class CrmStatisticsRankReqVO {
17 17  
18 18 @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
19 19 @NotNull(message = "部门 id 不能为空")
... ...