Commit a64da48aa36bd2fa3fd8adff0d166ae668191829

Authored by 芋道源码
Committed by Gitee
2 parents 4066ad3e be2431b7

!876 CRM:新增客户导入模版所属行业、客户等级、客户来源、客户地区单元格下拉选择

Merge pull request !876 from puhui999/develop
Showing 38 changed files with 896 additions and 392 deletions
yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java
@@ -4,6 +4,7 @@ import cn.hutool.core.io.resource.ResourceUtil; @@ -4,6 +4,7 @@ import cn.hutool.core.io.resource.ResourceUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.text.csv.CsvRow; 5 import cn.hutool.core.text.csv.CsvRow;
6 import cn.hutool.core.text.csv.CsvUtil; 6 import cn.hutool.core.text.csv.CsvUtil;
  7 +import cn.hutool.core.util.ObjUtil;
7 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 8 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
8 import cn.iocoder.yudao.framework.ip.core.Area; 9 import cn.iocoder.yudao.framework.ip.core.Area;
9 import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; 10 import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
@@ -17,6 +18,7 @@ import java.util.Map; @@ -17,6 +18,7 @@ import java.util.Map;
17 import java.util.function.Function; 18 import java.util.function.Function;
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.convertList;
  21 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst;
20 22
21 /** 23 /**
22 * 区域工具类 24 * 区域工具类
@@ -75,6 +77,58 @@ public class AreaUtils { @@ -75,6 +77,58 @@ public class AreaUtils {
75 } 77 }
76 78
77 /** 79 /**
  80 + * 获得指定区域对应的编号
  81 + *
  82 + * @param path 区域编号
  83 + * @return 编号
  84 + */
  85 + public static Area getArea(String path) {
  86 + String[] paths = path.split("/");
  87 + Area area = null;
  88 + for (int i = 0; i < paths.length; i++) {
  89 + final int finalI = i;
  90 + if (area == null) {
  91 + area = findFirst(convertList(areas.values(), a -> a), item -> ObjUtil.equal(paths[finalI], item.getName()));
  92 + continue;
  93 + }
  94 + area = findFirst(area.getChildren(), item -> ObjUtil.equal(paths[finalI], item.getName()));
  95 + }
  96 + return area;
  97 + }
  98 +
  99 + /**
  100 + * 获取所有节点的全路径名称如:河南省/石家庄市/新华区
  101 + *
  102 + * @param areas 地区树
  103 + * @return 所有节点的全路径名称
  104 + */
  105 + public static List<String> getAllAreaNodePaths(List<Area> areas) {
  106 + List<String> paths = new ArrayList<>();
  107 + areas.forEach(area -> traverse(area, "", paths));
  108 + return paths;
  109 + }
  110 +
  111 + /**
  112 + * 构建一棵树的所有节点的全路径名称,并将其存储为 "祖先/父级/子级" 的形式
  113 + *
  114 + * @param node 父节点
  115 + * @param path 全路径名称
  116 + * @param paths 全路径名称列表
  117 + */
  118 + private static void traverse(Area node, String path, List<String> paths) {
  119 + if (node == null) {
  120 + return;
  121 + }
  122 + // 构建当前节点的路径
  123 + String currentPath = path.isEmpty() ? node.getName() : path + "/" + node.getName();
  124 + paths.add(currentPath);
  125 + // 递归遍历子节点
  126 + for (Area child : node.getChildren()) {
  127 + traverse(child, currentPath, paths);
  128 + }
  129 + }
  130 +
  131 + /**
78 * 格式化区域 132 * 格式化区域
79 * 133 *
80 * @param id 区域编号 134 * @param id 区域编号
@@ -88,13 +142,13 @@ public class AreaUtils { @@ -88,13 +142,13 @@ public class AreaUtils {
88 * 格式化区域 142 * 格式化区域
89 * 143 *
90 * 例如说: 144 * 例如说:
91 - * 1. id = “静安区”时:上海 上海市 静安区  
92 - * 2. id = “上海市”时:上海 上海市  
93 - * 3. id = “上海”时:上海  
94 - * 4. id = “美国”时:美国 145 + * 1. id = “静安区”时:上海 上海市 静安区
  146 + * 2. id = “上海市”时:上海 上海市
  147 + * 3. id = “上海”时:上海
  148 + * 4. id = “美国”时:美国
95 * 当区域在中国时,默认不显示中国 149 * 当区域在中国时,默认不显示中国
96 * 150 *
97 - * @param id 区域编号 151 + * @param id 区域编号
98 * @param separator 分隔符 152 * @param separator 分隔符
99 * @return 格式化后的区域 153 * @return 格式化后的区域
100 */ 154 */
yudao-framework/yudao-spring-boot-starter-excel/pom.xml
@@ -46,6 +46,11 @@ @@ -46,6 +46,11 @@
46 <groupId>com.alibaba</groupId> 46 <groupId>com.alibaba</groupId>
47 <artifactId>easyexcel</artifactId> 47 <artifactId>easyexcel</artifactId>
48 </dependency> 48 </dependency>
  49 + <dependency>
  50 + <groupId>cn.iocoder.boot</groupId>
  51 + <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
  52 + <scope>provided</scope> <!-- 设置为 provided,只有 ExcelUtils 使用 -->
  53 + </dependency>
49 </dependencies> 54 </dependencies>
50 55
51 </project> 56 </project>
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.excel.core.convert;
  2 +
  3 +import cn.hutool.core.convert.Convert;
  4 +import cn.iocoder.yudao.framework.ip.core.Area;
  5 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
  6 +import com.alibaba.excel.converters.Converter;
  7 +import com.alibaba.excel.enums.CellDataTypeEnum;
  8 +import com.alibaba.excel.metadata.GlobalConfiguration;
  9 +import com.alibaba.excel.metadata.data.ReadCellData;
  10 +import com.alibaba.excel.metadata.property.ExcelContentProperty;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +
  13 +/**
  14 + * Excel 数据地区转换器
  15 + *
  16 + * @author HUIHUI
  17 + */
  18 +@Slf4j
  19 +public class AreaConvert implements Converter<Object> {
  20 +
  21 + @Override
  22 + public Class<?> supportJavaTypeKey() {
  23 + throw new UnsupportedOperationException("暂不支持,也不需要");
  24 + }
  25 +
  26 + @Override
  27 + public CellDataTypeEnum supportExcelTypeKey() {
  28 + throw new UnsupportedOperationException("暂不支持,也不需要");
  29 + }
  30 +
  31 + @Override
  32 + public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty,
  33 + GlobalConfiguration globalConfiguration) {
  34 + // 解析地区编号
  35 + String label = readCellData.getStringValue();
  36 + Area area = AreaUtils.getArea(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-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.iocoder.yudao.framework.common.core.KeyValue;
  5 +import com.alibaba.excel.write.handler.SheetWriteHandler;
  6 +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
  7 +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
  8 +import org.apache.poi.hssf.usermodel.HSSFDataValidation;
  9 +import org.apache.poi.ss.usermodel.*;
  10 +import org.apache.poi.ss.util.CellRangeAddressList;
  11 +
  12 +import java.util.Comparator;
  13 +import java.util.List;
  14 +
  15 +/**
  16 + * 基于固定 sheet 实现下拉框
  17 + *
  18 + * @author HUIHUI
  19 + */
  20 +public class SelectSheetWriteHandler implements SheetWriteHandler {
  21 +
  22 + private final List<KeyValue<Integer, List<String>>> selectMap;
  23 +
  24 + private static final char[] ALPHABET = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
  25 + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
  26 +
  27 + public SelectSheetWriteHandler(List<KeyValue<Integer, List<String>>> selectMap) {
  28 + selectMap.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错
  29 + this.selectMap = selectMap;
  30 + }
  31 +
  32 + @Override
  33 + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
  34 + if (selectMap == null || CollUtil.isEmpty(selectMap)) {
  35 + return;
  36 + }
  37 + // 需要设置下拉框的sheet页
  38 + Sheet curSheet = writeSheetHolder.getSheet();
  39 + DataValidationHelper helper = curSheet.getDataValidationHelper();
  40 + String dictSheetName = "字典sheet";
  41 + Workbook workbook = writeWorkbookHolder.getWorkbook();
  42 + // 数据字典的sheet页
  43 + Sheet dictSheet = workbook.createSheet(dictSheetName);
  44 + for (KeyValue<Integer, List<String>> keyValue : selectMap) {
  45 + // 设置下拉单元格的首行、末行、首列、末列
  46 + CellRangeAddressList rangeAddressList = new CellRangeAddressList(1, 65533, keyValue.getKey(), keyValue.getKey());
  47 + int rowLen = keyValue.getValue().size();
  48 + // 设置字典sheet页的值 每一列一个字典项
  49 + for (int i = 0; i < rowLen; i++) {
  50 + Row row = dictSheet.getRow(i);
  51 + if (row == null) {
  52 + row = dictSheet.createRow(i);
  53 + }
  54 + row.createCell(keyValue.getKey()).setCellValue(keyValue.getValue().get(i));
  55 + }
  56 + String excelColumn = getExcelColumn(keyValue.getKey());
  57 + // 下拉框数据来源 eg:字典sheet!$B1:$B2
  58 + String refers = dictSheetName + "!$" + excelColumn + "$1:$" + excelColumn + "$" + rowLen;
  59 + // 创建可被其他单元格引用的名称
  60 + Name name = workbook.createName();
  61 + // 设置名称的名字
  62 + name.setNameName("dict" + keyValue.getKey());
  63 + // 设置公式
  64 + name.setRefersToFormula(refers);
  65 + // 设置引用约束
  66 + DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + keyValue.getKey());
  67 + // 设置约束
  68 + DataValidation validation = helper.createValidation(constraint, rangeAddressList);
  69 + if (validation instanceof HSSFDataValidation) {
  70 + validation.setSuppressDropDownArrow(false);
  71 + } else {
  72 + validation.setSuppressDropDownArrow(true);
  73 + validation.setShowErrorBox(true);
  74 + }
  75 + // 阻止输入非下拉框的值
  76 + validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
  77 + validation.createErrorBox("提示", "此值不存在于下拉选择中!");
  78 + // 添加下拉框约束
  79 + writeSheetHolder.getSheet().addValidationData(validation);
  80 + }
  81 + }
  82 +
  83 + /**
  84 + * 将数字列转化成为字母列
  85 + *
  86 + * @param num 数字
  87 + * @return 字母
  88 + */
  89 + private String getExcelColumn(int num) {
  90 + String column = "";
  91 + int len = ALPHABET.length - 1;
  92 + int first = num / len;
  93 + int second = num % len;
  94 + if (num <= len) {
  95 + column = ALPHABET[num] + "";
  96 + } else {
  97 + column = ALPHABET[first - 1] + "";
  98 + if (second == 0) {
  99 + column = column + ALPHABET[len] + "";
  100 + } else {
  101 + column = column + ALPHABET[second - 1] + "";
  102 + }
  103 + }
  104 + return column;
  105 + }
  106 +
  107 +}
0 \ No newline at end of file 108 \ No newline at end of file
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
1 package cn.iocoder.yudao.framework.excel.core.util; 1 package cn.iocoder.yudao.framework.excel.core.util;
2 2
  3 +import cn.iocoder.yudao.framework.common.core.KeyValue;
  4 +import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler;
3 import com.alibaba.excel.EasyExcel; 5 import com.alibaba.excel.EasyExcel;
4 import com.alibaba.excel.converters.longconverter.LongStringConverter; 6 import com.alibaba.excel.converters.longconverter.LongStringConverter;
5 import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; 7 import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
  8 +import jakarta.servlet.http.HttpServletResponse;
6 import org.springframework.web.multipart.MultipartFile; 9 import org.springframework.web.multipart.MultipartFile;
7 10
8 -import jakarta.servlet.http.HttpServletResponse;  
9 import java.io.IOException; 11 import java.io.IOException;
10 import java.net.URLEncoder; 12 import java.net.URLEncoder;
11 import java.nio.charset.StandardCharsets; 13 import java.nio.charset.StandardCharsets;
@@ -21,12 +23,12 @@ public class ExcelUtils { @@ -21,12 +23,12 @@ public class ExcelUtils {
21 /** 23 /**
22 * 将列表以 Excel 响应给前端 24 * 将列表以 Excel 响应给前端
23 * 25 *
24 - * @param response 响应  
25 - * @param filename 文件名 26 + * @param response 响应
  27 + * @param filename 文件名
26 * @param sheetName Excel sheet 名 28 * @param sheetName Excel sheet 名
27 - * @param head Excel head 头  
28 - * @param data 数据列表哦  
29 - * @param <T> 泛型,保证 head 和 data 类型的一致性 29 + * @param head Excel head 头
  30 + * @param data 数据列表哦
  31 + * @param <T> 泛型,保证 head 和 data 类型的一致性
30 * @throws IOException 写入失败的情况 32 * @throws IOException 写入失败的情况
31 */ 33 */
32 public static <T> void write(HttpServletResponse response, String filename, String sheetName, 34 public static <T> void write(HttpServletResponse response, String filename, String sheetName,
@@ -38,12 +40,37 @@ public class ExcelUtils { @@ -38,12 +40,37 @@ public class ExcelUtils {
38 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度 40 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
39 .sheet(sheetName).doWrite(data); 41 .sheet(sheetName).doWrite(data);
40 // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了 42 // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
41 - response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8)); 43 + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
  44 + response.setContentType("application/vnd.ms-excel;charset=UTF-8");
  45 + }
  46 +
  47 + /**
  48 + * 将列表以 Excel 响应给前端
  49 + *
  50 + * @param response 响应
  51 + * @param filename 文件名
  52 + * @param sheetName Excel sheet 名
  53 + * @param head Excel head 头
  54 + * @param data 数据列表哦
  55 + * @param selectMap 下拉选择数据 Map
  56 + * @throws IOException 写入失败的情况
  57 + */
  58 + public static <T> void write(HttpServletResponse response, String filename, String sheetName,
  59 + Class<T> head, List<T> data, List<KeyValue<Integer, List<String>>> selectMap) throws IOException {
  60 + // 输出 Excel
  61 + EasyExcel.write(response.getOutputStream(), head)
  62 + .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
  63 + .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度
  64 + .registerWriteHandler(new SelectSheetWriteHandler(selectMap)) // 基于固定 sheet 实现下拉框
  65 + .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
  66 + .sheet(sheetName).doWrite(data);
  67 + // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了
  68 + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));
42 response.setContentType("application/vnd.ms-excel;charset=UTF-8"); 69 response.setContentType("application/vnd.ms-excel;charset=UTF-8");
43 } 70 }
44 71
45 public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException { 72 public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {
46 - return EasyExcel.read(file.getInputStream(), head, null) 73 + return EasyExcel.read(file.getInputStream(), head, null)
47 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 74 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
48 .doReadAllSync(); 75 .doReadAllSync();
49 } 76 }
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -13,7 +13,7 @@ public interface ErrorCodeConstants { @@ -13,7 +13,7 @@ public interface ErrorCodeConstants {
13 ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在"); 13 ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在");
14 ErrorCode CONTRACT_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_001, "合同更新失败,原因:合同不是草稿状态"); 14 ErrorCode CONTRACT_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_001, "合同更新失败,原因:合同不是草稿状态");
15 ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态"); 15 ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态");
16 - ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是处理中状态"); 16 + ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态");
17 17
18 // ========== 线索管理 1-020-001-000 ========== 18 // ========== 线索管理 1-020-001-000 ==========
19 ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在"); 19 ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
@@ -33,9 +33,14 @@ public interface ErrorCodeConstants { @@ -33,9 +33,14 @@ public interface ErrorCodeConstants {
33 33
34 // ========== 回款 1-020-004-000 ========== 34 // ========== 回款 1-020-004-000 ==========
35 ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在"); 35 ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在");
  36 + 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, "更新回款审核状态失败,原因:回款不是审核中状态");
36 40
37 - // ========== 合同管理 1-020-005-000 ========== 41 + // ========== 回款计划 1-020-005-000 ==========
38 ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在"); 42 ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在");
  43 + ErrorCode RECEIVABLE_PLAN_UPDATE_FAIL = new ErrorCode(1_020_006_000, "更想回款计划失败,原因:{}");
39 44
40 // ========== 客户管理 1_020_006_000 ========== 45 // ========== 客户管理 1_020_006_000 ==========
41 ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在"); 46 ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在");
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
@@ -141,6 +141,8 @@ public interface LogRecordConstants { @@ -141,6 +141,8 @@ public interface LogRecordConstants {
141 String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}"; 141 String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}";
142 String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款"; 142 String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款";
143 String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款"; 143 String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款";
  144 + String CRM_RECEIVABLE_SUBMIT_SUB_TYPE = "提交回款审批";
  145 + String CRM_RECEIVABLE_SUBMIT_SUCCESS = "提交编号为【{{#receivableNo}}】的回款审批成功";
144 146
145 // ======================= CRM_RECEIVABLE_PLAN 回款计划 ======================= 147 // ======================= CRM_RECEIVABLE_PLAN 回款计划 =======================
146 148
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/receivable/CrmReturnTypeEnum.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 CrmReturnTypeEnum implements IntArrayValuable {
  17 +
  18 + // 支票
  19 + CHECK(1, "支票"),
  20 + // 现金
  21 + CASH(2, "现金"),
  22 + // 邮政汇款
  23 + POSTAL_REMITTANCE(3, "邮政汇款"),
  24 + // 电汇
  25 + TELEGRAPHIC_TRANSFER(4, "电汇"),
  26 + // 网上转账
  27 + ONLINE_TRANSFER(5, "网上转账"),
  28 + // 支付宝
  29 + ALIPAY(6, "支付宝"),
  30 + // 微信支付
  31 + WECHAT_PAY(7, "微信支付"),
  32 + // 其他
  33 + OTHER(8, "其它");
  34 +
  35 + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmReturnTypeEnum::getType).toArray();
  36 +
  37 + /**
  38 + * 类型
  39 + */
  40 + private final Integer type;
  41 + /**
  42 + * 名称
  43 + */
  44 + private final String name;
  45 +
  46 + @Override
  47 + public int[] array() {
  48 + return ARRAYS;
  49 + }
  50 +
  51 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer; @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.map.MapUtil; 4 import cn.hutool.core.map.MapUtil;
  5 +import cn.iocoder.yudao.framework.common.core.KeyValue;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 8 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
@@ -10,6 +11,7 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; @@ -10,6 +11,7 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
10 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 11 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
11 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
12 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 13 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  14 +import cn.iocoder.yudao.framework.ip.core.Area;
13 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; 15 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
14 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 16 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
15 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; 17 import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*;
@@ -19,6 +21,7 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService @@ -19,6 +21,7 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService
19 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; 21 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
20 import cn.iocoder.yudao.module.system.api.dept.DeptApi; 22 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
21 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; 23 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  24 +import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
22 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 25 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
23 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 26 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
24 import io.swagger.v3.oas.annotations.Operation; 27 import io.swagger.v3.oas.annotations.Operation;
@@ -34,6 +37,7 @@ import org.springframework.web.bind.annotation.*; @@ -34,6 +37,7 @@ import org.springframework.web.bind.annotation.*;
34 37
35 import java.io.IOException; 38 import java.io.IOException;
36 import java.time.LocalDateTime; 39 import java.time.LocalDateTime;
  40 +import java.util.ArrayList;
37 import java.util.Arrays; 41 import java.util.Arrays;
38 import java.util.List; 42 import java.util.List;
39 import java.util.Map; 43 import java.util.Map;
@@ -44,6 +48,7 @@ import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; @@ -44,6 +48,7 @@ import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
44 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 48 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
45 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; 49 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
46 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; 50 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  51 +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;
47 import static java.util.Collections.singletonList; 52 import static java.util.Collections.singletonList;
48 53
49 @Tag(name = "管理后台 - CRM 客户") 54 @Tag(name = "管理后台 - CRM 客户")
@@ -61,6 +66,8 @@ public class CrmCustomerController { @@ -61,6 +66,8 @@ public class CrmCustomerController {
61 private DeptApi deptApi; 66 private DeptApi deptApi;
62 @Resource 67 @Resource
63 private AdminUserApi adminUserApi; 68 private AdminUserApi adminUserApi;
  69 + @Resource
  70 + private DictDataApi dictDataApi;
64 71
65 @PostMapping("/create") 72 @PostMapping("/create")
66 @Operation(summary = "创建客户") 73 @Operation(summary = "创建客户")
@@ -258,7 +265,7 @@ public class CrmCustomerController { @@ -258,7 +265,7 @@ public class CrmCustomerController {
258 .areaId(null).detailAddress("").remark("").build() 265 .areaId(null).detailAddress("").remark("").build()
259 ); 266 );
260 // 输出 267 // 输出
261 - ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list); 268 + ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list, builderSelectMap());
262 } 269 }
263 270
264 @PostMapping("/import") 271 @PostMapping("/import")
@@ -314,4 +321,21 @@ public class CrmCustomerController { @@ -314,4 +321,21 @@ public class CrmCustomerController {
314 return success(true); 321 return success(true);
315 } 322 }
316 323
  324 + private List<KeyValue<Integer, List<String>>> builderSelectMap() {
  325 + List<KeyValue<Integer, List<String>>> selectMap = new ArrayList<>();
  326 + // 获取地区下拉数据
  327 + Area area = AreaUtils.getArea(Area.ID_CHINA);
  328 + selectMap.add(new KeyValue<>(6, AreaUtils.getAllAreaNodePaths(area.getChildren())));
  329 + // 获取客户所属行业
  330 + List<String> customerIndustries = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_INDUSTRY);
  331 + selectMap.add(new KeyValue<>(8, customerIndustries));
  332 + // 获取客户等级
  333 + List<String> customerLevels = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_LEVEL);
  334 + selectMap.add(new KeyValue<>(9, customerLevels));
  335 + // 获取客户来源
  336 + List<String> customerSources = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_SOURCE);
  337 + selectMap.add(new KeyValue<>(10, customerSources));
  338 + return selectMap;
  339 + }
  340 +
317 } 341 }
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.customer; 1 package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2
3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
4 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; 5 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 import com.alibaba.excel.annotation.ExcelProperty; 6 import com.alibaba.excel.annotation.ExcelProperty;
6 import lombok.AllArgsConstructor; 7 import lombok.AllArgsConstructor;
@@ -39,14 +40,12 @@ public class CrmCustomerImportExcelVO { @@ -39,14 +40,12 @@ public class CrmCustomerImportExcelVO {
39 @ExcelProperty("邮箱") 40 @ExcelProperty("邮箱")
40 private String email; 41 private String email;
41 42
42 - // TODO @puhui999:需要选择省市区,需要研究下,怎么搞合理点;  
43 - @ExcelProperty("地区编号") 43 + @ExcelProperty(value = "地区", converter = AreaConvert.class)
44 private Integer areaId; 44 private Integer areaId;
45 45
46 @ExcelProperty("详细地址") 46 @ExcelProperty("详细地址")
47 private String detailAddress; 47 private String detailAddress;
48 48
49 - // TODO @puhui999:industryId、level、source 字段,可以研究下怎么搞下拉框  
50 @ExcelProperty(value = "所属行业", converter = DictConvert.class) 49 @ExcelProperty(value = "所属行业", converter = DictConvert.class)
51 @DictFormat(CRM_CUSTOMER_INDUSTRY) 50 @DictFormat(CRM_CUSTOMER_INDUSTRY)
52 private Integer industryId; 51 private Integer industryId;
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
@@ -5,12 +5,12 @@ import cn.hutool.core.lang.Assert; @@ -5,12 +5,12 @@ import cn.hutool.core.lang.Assert;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;  
11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; 11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
12 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; 12 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; 13 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO;
14 import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert; 14 import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert;
15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@@ -61,14 +61,14 @@ public class CrmReceivableController { @@ -61,14 +61,14 @@ public class CrmReceivableController {
61 @PostMapping("/create") 61 @PostMapping("/create")
62 @Operation(summary = "创建回款") 62 @Operation(summary = "创建回款")
63 @PreAuthorize("@ss.hasPermission('crm:receivable:create')") 63 @PreAuthorize("@ss.hasPermission('crm:receivable:create')")
64 - public CommonResult<Long> createReceivable(@Valid @RequestBody CrmReceivableCreateReqVO createReqVO) {  
65 - return success(receivableService.createReceivable(createReqVO, getLoginUserId())); 64 + public CommonResult<Long> createReceivable(@Valid @RequestBody CrmReceivableSaveReqVO createReqVO) {
  65 + return success(receivableService.createReceivable(createReqVO));
66 } 66 }
67 67
68 @PutMapping("/update") 68 @PutMapping("/update")
69 @Operation(summary = "更新回款") 69 @Operation(summary = "更新回款")
70 @PreAuthorize("@ss.hasPermission('crm:receivable:update')") 70 @PreAuthorize("@ss.hasPermission('crm:receivable:update')")
71 - public CommonResult<Boolean> updateReceivable(@Valid @RequestBody CrmReceivableUpdateReqVO updateReqVO) { 71 + public CommonResult<Boolean> updateReceivable(@Valid @RequestBody CrmReceivableSaveReqVO updateReqVO) {
72 receivableService.updateReceivable(updateReqVO); 72 receivableService.updateReceivable(updateReqVO);
73 return success(true); 73 return success(true);
74 } 74 }
@@ -88,7 +88,7 @@ public class CrmReceivableController { @@ -88,7 +88,7 @@ public class CrmReceivableController {
88 @PreAuthorize("@ss.hasPermission('crm:receivable:query')") 88 @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
89 public CommonResult<CrmReceivableRespVO> getReceivable(@RequestParam("id") Long id) { 89 public CommonResult<CrmReceivableRespVO> getReceivable(@RequestParam("id") Long id) {
90 CrmReceivableDO receivable = receivableService.getReceivable(id); 90 CrmReceivableDO receivable = receivableService.getReceivable(id);
91 - return success(CrmReceivableConvert.INSTANCE.convert(receivable)); 91 + return success(BeanUtils.toBean(receivable, CrmReceivableRespVO.class));
92 } 92 }
93 93
94 @GetMapping("/page") 94 @GetMapping("/page")
@@ -152,4 +152,12 @@ public class CrmReceivableController { @@ -152,4 +152,12 @@ public class CrmReceivableController {
152 return success(receivableService.getCheckReceivablesCount(getLoginUserId())); 152 return success(receivableService.getCheckReceivablesCount(getLoginUserId()));
153 } 153 }
154 154
  155 + @PutMapping("/submit")
  156 + @Operation(summary = "提交回款审批")
  157 + @PreAuthorize("@ss.hasPermission('crm:receivable:submit')")
  158 + public CommonResult<Boolean> submitContract(@RequestParam("id") Long id) {
  159 + receivableService.submitReceivable(id, getLoginUserId());
  160 + return success(true);
  161 + }
  162 +
155 } 163 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
@@ -5,12 +5,12 @@ import cn.hutool.core.lang.Assert; @@ -5,12 +5,12 @@ import cn.hutool.core.lang.Assert;
5 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils; 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;  
11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; 11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
12 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO; 12 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
13 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; 13 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO;
14 import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert; 14 import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert;
15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
@@ -67,14 +67,14 @@ public class CrmReceivablePlanController { @@ -67,14 +67,14 @@ public class CrmReceivablePlanController {
67 @PostMapping("/create") 67 @PostMapping("/create")
68 @Operation(summary = "创建回款计划") 68 @Operation(summary = "创建回款计划")
69 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:create')") 69 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:create')")
70 - public CommonResult<Long> createReceivablePlan(@Valid @RequestBody CrmReceivablePlanCreateReqVO createReqVO) {  
71 - return success(receivablePlanService.createReceivablePlan(createReqVO, getLoginUserId())); 70 + public CommonResult<Long> createReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO createReqVO) {
  71 + return success(receivablePlanService.createReceivablePlan(createReqVO));
72 } 72 }
73 73
74 @PutMapping("/update") 74 @PutMapping("/update")
75 @Operation(summary = "更新回款计划") 75 @Operation(summary = "更新回款计划")
76 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:update')") 76 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:update')")
77 - public CommonResult<Boolean> updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanUpdateReqVO updateReqVO) { 77 + public CommonResult<Boolean> updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO updateReqVO) {
78 receivablePlanService.updateReceivablePlan(updateReqVO); 78 receivablePlanService.updateReceivablePlan(updateReqVO);
79 return success(true); 79 return success(true);
80 } 80 }
@@ -94,7 +94,7 @@ public class CrmReceivablePlanController { @@ -94,7 +94,7 @@ public class CrmReceivablePlanController {
94 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')") 94 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
95 public CommonResult<CrmReceivablePlanRespVO> getReceivablePlan(@RequestParam("id") Long id) { 95 public CommonResult<CrmReceivablePlanRespVO> getReceivablePlan(@RequestParam("id") Long id) {
96 CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(id); 96 CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(id);
97 - return success(CrmReceivablePlanConvert.INSTANCE.convert(receivablePlan)); 97 + return success(BeanUtils.toBean(receivablePlan, CrmReceivablePlanRespVO.class));
98 } 98 }
99 99
100 @GetMapping("/page") 100 @GetMapping("/page")
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/CrmReceivablePlanRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 -import lombok.*; 4 +import lombok.Data;
  5 +
  6 +import java.math.BigDecimal;
5 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
6 8
7 @Schema(description = "管理后台 - CRM 回款计划 Response VO") 9 @Schema(description = "管理后台 - CRM 回款计划 Response VO")
8 @Data 10 @Data
9 -@EqualsAndHashCode(callSuper = true)  
10 -@ToString(callSuper = true)  
11 -public class CrmReceivablePlanRespVO extends CrmReceivablePlanBaseVO { 11 +public class CrmReceivablePlanRespVO {
12 12
13 - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25153") 13 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
14 private Long id; 14 private Long id;
15 15
  16 + @Schema(description = "回款编号", example = "19852")
  17 + private Long receivableId;
  18 +
  19 + @Schema(description = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  20 + private Integer period;
  21 +
  22 + @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  23 + private BigDecimal price;
  24 +
  25 + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  26 + private LocalDateTime returnTime;
  27 +
  28 + @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  29 + private Integer remindDays;
  30 +
  31 + @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  32 + private LocalDateTime remindTime;
  33 +
  34 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  35 + private Long customerId;
  36 +
  37 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  38 + private Long contractId;
  39 +
  40 + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  41 + private Long ownerUserId;
  42 +
  43 + @Schema(description = "显示顺序")
  44 + private Integer sort;
  45 +
  46 + @Schema(description = "备注", example = "备注")
  47 + private String remark;
  48 +
16 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 49 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
17 private LocalDateTime createTime; 50 private LocalDateTime createTime;
18 51
@@ -34,7 +67,7 @@ public class CrmReceivablePlanRespVO extends CrmReceivablePlanBaseVO { @@ -34,7 +67,7 @@ public class CrmReceivablePlanRespVO extends CrmReceivablePlanBaseVO {
34 @Schema(description = "完成状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") 67 @Schema(description = "完成状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
35 private Boolean finishStatus; 68 private Boolean finishStatus;
36 69
37 - @Schema(description = "回款方式", example = "1") // 来自 Receivable 的 returnType 字段  
38 - private Integer returnType; 70 + @Schema(description = "回款方式", example = "1")
  71 + private Integer returnType; // 来自 Receivable 的 returnType 字段
39 72
40 } 73 }
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 = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  18 + @NotNull(message = "期数不能为空")
  19 + private Integer period;
  20 +
  21 + @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  22 + @NotNull(message = "计划回款金额不能为空")
  23 + private BigDecimal price;
  24 +
  25 + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  26 + @NotNull(message = "计划回款日期不能为空")
  27 + private LocalDateTime returnTime;
  28 +
  29 + @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  30 + @NotNull(message = "提前几天提醒不能为空")
  31 + private Integer remindDays;
  32 +
  33 + @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  34 + @NotNull(message = "提醒日期不能为空")
  35 + private LocalDateTime remindTime;
  36 +
  37 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  38 + @NotNull(message = "客户编号不能为空")
  39 + private Long customerId;
  40 +
  41 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  42 + @NotNull(message = "合同编号不能为空")
  43 + private Long contractId;
  44 +
  45 + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  46 + @NotNull(message = "负责人编号不能为空")
  47 + private Long ownerUserId;
  48 +
  49 + @Schema(description = "显示顺序")
  50 + private Integer sort;
  51 +
  52 + @Schema(description = "备注", example = "备注")
  53 + private String remark;
  54 +
  55 +}
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/CrmReceivableRespVO.java
1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
2 2
3 import io.swagger.v3.oas.annotations.media.Schema; 3 import io.swagger.v3.oas.annotations.media.Schema;
4 -import lombok.*; 4 +import lombok.Data;
  5 +
  6 +import java.math.BigDecimal;
5 import java.time.LocalDateTime; 7 import java.time.LocalDateTime;
6 8
7 // TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能 9 // TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能
8 @Schema(description = "管理后台 - CRM 回款 Response VO") 10 @Schema(description = "管理后台 - CRM 回款 Response VO")
9 @Data 11 @Data
10 -@EqualsAndHashCode(callSuper = true)  
11 -@ToString(callSuper = true)  
12 -public class CrmReceivableRespVO extends CrmReceivableBaseVO { 12 +public class CrmReceivableRespVO {
13 13
14 - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25787") 14 + @Schema(description = "编号", example = "25787")
15 private Long id; 15 private Long id;
16 16
  17 + @Schema(description = "回款编号", example = "31177")
  18 + private String no;
  19 +
  20 + @Schema(description = "回款计划编号", example = "1024")
  21 + private Long planId;
  22 +
  23 + @Schema(description = "回款方式", example = "2")
  24 + private Integer returnType;
  25 +
  26 + @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  27 + private BigDecimal price;
  28 +
  29 + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  30 + private LocalDateTime returnTime;
  31 +
  32 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  33 + private Long customerId;
  34 +
  35 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  36 + private Long contractId;
  37 +
  38 + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  39 + private Long ownerUserId;
  40 +
  41 + @Schema(description = "显示顺序")
  42 + private Integer sort;
  43 +
  44 + @Schema(description = "备注", example = "备注")
  45 + private String remark;
  46 +
17 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 47 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
18 private LocalDateTime createTime; 48 private LocalDateTime createTime;
19 49
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.CrmReturnTypeEnum;
  5 +import io.swagger.v3.oas.annotations.media.Schema;
  6 +import jakarta.validation.constraints.NotNull;
  7 +import lombok.Data;
  8 +
  9 +import java.math.BigDecimal;
  10 +import java.time.LocalDateTime;
  11 +
  12 +@Schema(description = "管理后台 - CRM 回款新增/修改 Request VO")
  13 +@Data
  14 +public class CrmReceivableSaveReqVO {
  15 +
  16 + @Schema(description = "编号", example = "25787")
  17 + private Long id;
  18 +
  19 + @Schema(description = "回款编号", example = "31177")
  20 + private String no;
  21 +
  22 + @Schema(description = "回款计划编号", example = "1024")
  23 + private Long planId; // 不是通过回款计划创建的回款没有回款计划编号
  24 +
  25 + @Schema(description = "回款方式", example = "2")
  26 + @InEnum(CrmReturnTypeEnum.class)
  27 + private Integer returnType;
  28 +
  29 + @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000")
  30 + @NotNull(message = "回款金额不能为空")
  31 + private BigDecimal price;
  32 +
  33 + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
  34 + @NotNull(message = "计划回款日期不能为空")
  35 + private LocalDateTime returnTime;
  36 +
  37 + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  38 + @NotNull(message = "客户编号不能为空")
  39 + private Long customerId;
  40 +
  41 + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  42 + @NotNull(message = "合同编号不能为空")
  43 + private Long contractId;
  44 +
  45 + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
  46 + @NotNull(message = "负责人编号不能为空")
  47 + private Long ownerUserId;
  48 +
  49 + @Schema(description = "显示顺序")
  50 + private Integer sort;
  51 +
  52 + @Schema(description = "备注", example = "备注")
  53 + private String remark;
  54 +
  55 +}
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/convert/receivable/CrmReceivableConvert.java
@@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.crm.convert.receivable; @@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.crm.convert.receivable;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;  
6 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; 5 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO;
7 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO;  
8 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 6 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
9 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 7 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
10 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; 8 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
@@ -28,12 +26,6 @@ public interface CrmReceivableConvert { @@ -28,12 +26,6 @@ public interface CrmReceivableConvert {
28 26
29 CrmReceivableConvert INSTANCE = Mappers.getMapper(CrmReceivableConvert.class); 27 CrmReceivableConvert INSTANCE = Mappers.getMapper(CrmReceivableConvert.class);
30 28
31 - CrmReceivableDO convert(CrmReceivableCreateReqVO bean);  
32 -  
33 - CrmReceivableDO convert(CrmReceivableUpdateReqVO bean);  
34 -  
35 - CrmReceivableRespVO convert(CrmReceivableDO bean);  
36 -  
37 default PageResult<CrmReceivableRespVO> convertPage(PageResult<CrmReceivableDO> pageResult, Map<Long, AdminUserRespDTO> userMap, 29 default PageResult<CrmReceivableRespVO> convertPage(PageResult<CrmReceivableDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
38 List<CrmCustomerDO> customerList, List<CrmContractDO> contractList) { 30 List<CrmCustomerDO> customerList, List<CrmContractDO> contractList) {
39 PageResult<CrmReceivableRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmReceivableRespVO.class); 31 PageResult<CrmReceivableRespVO> voPageResult = BeanUtils.toBean(pageResult, CrmReceivableRespVO.class);
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java
@@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.crm.convert.receivable; @@ -2,9 +2,7 @@ package cn.iocoder.yudao.module.crm.convert.receivable;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;  
6 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO; 5 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
7 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO;  
8 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 6 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
9 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 7 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
10 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; 8 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
@@ -29,12 +27,6 @@ public interface CrmReceivablePlanConvert { @@ -29,12 +27,6 @@ public interface CrmReceivablePlanConvert {
29 27
30 CrmReceivablePlanConvert INSTANCE = Mappers.getMapper(CrmReceivablePlanConvert.class); 28 CrmReceivablePlanConvert INSTANCE = Mappers.getMapper(CrmReceivablePlanConvert.class);
31 29
32 - CrmReceivablePlanDO convert(CrmReceivablePlanCreateReqVO bean);  
33 -  
34 - CrmReceivablePlanDO convert(CrmReceivablePlanUpdateReqVO bean);  
35 -  
36 - CrmReceivablePlanRespVO convert(CrmReceivablePlanDO bean);  
37 -  
38 default PageResult<CrmReceivablePlanRespVO> convertPage(PageResult<CrmReceivablePlanDO> pageResult, Map<Long, AdminUserRespDTO> userMap, 30 default PageResult<CrmReceivablePlanRespVO> convertPage(PageResult<CrmReceivablePlanDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
39 List<CrmCustomerDO> customerList, List<CrmContractDO> contractList, 31 List<CrmCustomerDO> customerList, List<CrmContractDO> contractList,
40 List<CrmReceivableDO> receivableList) { 32 List<CrmReceivableDO> receivableList) {
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java
@@ -2,11 +2,16 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.receivable; @@ -2,11 +2,16 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.receivable;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 4 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
  5 +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
  6 +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
  7 +import cn.iocoder.yudao.module.crm.enums.receivable.CrmReturnTypeEnum;
  8 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
5 import com.baomidou.mybatisplus.annotation.KeySequence; 9 import com.baomidou.mybatisplus.annotation.KeySequence;
6 import com.baomidou.mybatisplus.annotation.TableId; 10 import com.baomidou.mybatisplus.annotation.TableId;
7 import com.baomidou.mybatisplus.annotation.TableName; 11 import com.baomidou.mybatisplus.annotation.TableName;
8 import lombok.*; 12 import lombok.*;
9 13
  14 +import java.math.BigDecimal;
10 import java.time.LocalDateTime; 15 import java.time.LocalDateTime;
11 16
12 /** 17 /**
@@ -33,64 +38,55 @@ public class CrmReceivableDO extends BaseDO { @@ -33,64 +38,55 @@ public class CrmReceivableDO extends BaseDO {
33 * 回款编号 38 * 回款编号
34 */ 39 */
35 private String no; 40 private String no;
36 - // TODO @liuhongfeng:“对应实体”,参考别的模块,关联 {@link TableField.MetaInfo#getJdbcType()}  
37 /** 41 /**
38 - * 回款计划  
39 - *  
40 - * TODO @liuhongfeng:这个字段什么时候更新,也可以写下  
41 - *  
42 - * 对应实体 {@link CrmReceivablePlanDO} 42 + * 回款计划编号,关联 {@link CrmReceivablePlanDO#getId()}
43 */ 43 */
44 private Long planId; 44 private Long planId;
45 /** 45 /**
46 - * 客户 ID  
47 - *  
48 - * 对应实体 {@link cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO} 46 + * 客户编号,关联 {@link CrmCustomerDO#getId()}
49 */ 47 */
50 private Long customerId; 48 private Long customerId;
51 /** 49 /**
52 - * 合同 ID  
53 - *  
54 - * 对应实体 {@link CrmContractDO} 50 + * 合同编号,关联 {@link CrmContractDO#getId()}
55 */ 51 */
56 private Long contractId; 52 private Long contractId;
57 /** 53 /**
58 - * 工作流编号  
59 - *  
60 - * TODO @liuhongfeng:这个字段,后续要写下关联的实体哈 54 + * 负责人编号,关联 {@link AdminUserRespDTO#getId()}
61 */ 55 */
62 - private Long processInstanceId; 56 + private Long ownerUserId;
  57 +
63 /** 58 /**
64 * 回款日期 59 * 回款日期
65 */ 60 */
66 private LocalDateTime returnTime; 61 private LocalDateTime returnTime;
67 - // TODO @liuhongfeng:少个枚举  
68 /** 62 /**
69 - * 回款方式 63 + * 回款方式,关联枚举{@link CrmReturnTypeEnum}
70 */ 64 */
71 private Integer returnType; 65 private Integer returnType;
72 /** 66 /**
73 - * 回款金额 67 + * 计划回款金额,单位:元
74 */ 68 */
75 - private Integer price;  
76 - // TODO @liuhongfeng:少关联实体;  
77 - /**  
78 - * 负责人  
79 - */  
80 - private Long ownerUserId; 69 + private BigDecimal price;
81 /** 70 /**
82 * 显示顺序 71 * 显示顺序
83 */ 72 */
84 private Integer sort; 73 private Integer sort;
85 /** 74 /**
86 - * 审核状态 75 + * 备注
  76 + */
  77 + private String remark;
  78 +
  79 + /**
  80 + * 工作流编号
87 * 81 *
88 - * 枚举 {@link cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum} 82 + * 关联 ProcessInstance 的 id 属性
89 */ 83 */
90 - private Integer auditStatus; 84 + private String processInstanceId;
91 /** 85 /**
92 - * 备注 86 + * 审批状态
  87 + *
  88 + * 枚举 {@link CrmAuditStatusEnum}
93 */ 89 */
94 - private String remark; 90 + private Integer auditStatus;
95 91
96 } 92 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java
1 package cn.iocoder.yudao.module.crm.dal.dataobject.receivable; 1 package cn.iocoder.yudao.module.crm.dal.dataobject.receivable;
2 2
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
  5 +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
  6 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
4 import com.baomidou.mybatisplus.annotation.KeySequence; 7 import com.baomidou.mybatisplus.annotation.KeySequence;
5 import com.baomidou.mybatisplus.annotation.TableId; 8 import com.baomidou.mybatisplus.annotation.TableId;
6 import com.baomidou.mybatisplus.annotation.TableName; 9 import com.baomidou.mybatisplus.annotation.TableName;
7 import lombok.*; 10 import lombok.*;
8 11
  12 +import java.math.BigDecimal;
9 import java.time.LocalDateTime; 13 import java.time.LocalDateTime;
10 14
11 /** 15 /**
@@ -24,7 +28,7 @@ import java.time.LocalDateTime; @@ -24,7 +28,7 @@ import java.time.LocalDateTime;
24 public class CrmReceivablePlanDO extends BaseDO { 28 public class CrmReceivablePlanDO extends BaseDO {
25 29
26 /** 30 /**
27 - * ID 31 + * 编号
28 */ 32 */
29 @TableId 33 @TableId
30 private Long id; 34 private Long id;
@@ -33,19 +37,13 @@ public class CrmReceivablePlanDO extends BaseDO { @@ -33,19 +37,13 @@ public class CrmReceivablePlanDO extends BaseDO {
33 */ 37 */
34 private Integer period; 38 private Integer period;
35 /** 39 /**
36 - * 回款ID  
37 - *  
38 - * TODO @liuhongfeng:少关联实体; 40 + * 回款编号,关联 {@link CrmReceivableDO#getId()}
39 */ 41 */
40 private Long receivableId; 42 private Long receivableId;
41 /** 43 /**
42 - * 完成状态 44 + * 计划回款金额,单位:元
43 */ 45 */
44 - private Boolean finishStatus;  
45 - /**  
46 - * 计划回款金额,单位:分  
47 - */  
48 - private Integer price; 46 + private BigDecimal price;
49 /** 47 /**
50 * 计划回款日期 48 * 计划回款日期
51 */ 49 */
@@ -59,21 +57,15 @@ public class CrmReceivablePlanDO extends BaseDO { @@ -59,21 +57,15 @@ public class CrmReceivablePlanDO extends BaseDO {
59 */ 57 */
60 private LocalDateTime remindTime; 58 private LocalDateTime remindTime;
61 /** 59 /**
62 - * 客户 ID  
63 - *  
64 - * TODO @liuhongfeng:少关联实体; 60 + * 客户编号,关联 {@link CrmCustomerDO#getId()}
65 */ 61 */
66 private Long customerId; 62 private Long customerId;
67 /** 63 /**
68 - * 合同 ID  
69 - *  
70 - * TODO @liuhongfeng:少关联实体; 64 + * 合同编号,关联 {@link CrmContractDO#getId()}
71 */ 65 */
72 private Long contractId; 66 private Long contractId;
73 /** 67 /**
74 - * 负责人 ID  
75 - *  
76 - * TODO @liuhongfeng:少关联实体; 68 + * 负责人编号,关联 {@link AdminUserRespDTO#getId()}
77 */ 69 */
78 private Long ownerUserId; 70 private Long ownerUserId;
79 /** 71 /**
@@ -85,4 +77,9 @@ public class CrmReceivablePlanDO extends BaseDO { @@ -85,4 +77,9 @@ public class CrmReceivablePlanDO extends BaseDO {
85 */ 77 */
86 private String remark; 78 private String remark;
87 79
  80 + /**
  81 + * 完成状态
  82 + */
  83 + private Boolean finishStatus;
  84 +
88 } 85 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java
@@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO @@ -10,7 +10,6 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO
10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; 10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
11 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; 11 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
12 import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; 12 import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
13 -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;  
14 import org.apache.ibatis.annotations.Mapper; 13 import org.apache.ibatis.annotations.Mapper;
15 14
16 import java.time.LocalDateTime; 15 import java.time.LocalDateTime;
@@ -25,12 +24,6 @@ import java.util.List; @@ -25,12 +24,6 @@ import java.util.List;
25 @Mapper 24 @Mapper
26 public interface CrmReceivablePlanMapper extends BaseMapperX<CrmReceivablePlanDO> { 25 public interface CrmReceivablePlanMapper extends BaseMapperX<CrmReceivablePlanDO> {
27 26
28 - default int updateOwnerUserIdById(Long id, Long ownerUserId) {  
29 - return update(new LambdaUpdateWrapper<CrmReceivablePlanDO>()  
30 - .eq(CrmReceivablePlanDO::getId, id)  
31 - .set(CrmReceivablePlanDO::getOwnerUserId, ownerUserId));  
32 - }  
33 -  
34 default PageResult<CrmReceivablePlanDO> selectPageByCustomerId(CrmReceivablePlanPageReqVO reqVO) { 27 default PageResult<CrmReceivablePlanDO> selectPageByCustomerId(CrmReceivablePlanPageReqVO reqVO) {
35 return selectPage(reqVO, new LambdaQueryWrapperX<CrmReceivablePlanDO>() 28 return selectPage(reqVO, new LambdaQueryWrapperX<CrmReceivablePlanDO>()
36 .eq(CrmReceivablePlanDO::getCustomerId, reqVO.getCustomerId()) // 必须传递 29 .eq(CrmReceivablePlanDO::getCustomerId, reqVO.getCustomerId()) // 必须传递
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
@@ -10,7 +10,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -10,7 +10,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
10 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; 10 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
11 import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; 11 import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
12 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; 12 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
13 -import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;  
14 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; 13 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
15 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; 14 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO;
16 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; 15 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
@@ -48,6 +47,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU @@ -48,6 +47,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
48 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; 47 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
49 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; 48 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
50 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; 49 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
  50 +import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus;
51 51
52 /** 52 /**
53 * CRM 合同 Service 实现类 53 * CRM 合同 Service 实现类
@@ -303,10 +303,7 @@ public class CrmContractServiceImpl implements CrmContractService { @@ -303,10 +303,7 @@ public class CrmContractServiceImpl implements CrmContractService {
303 } 303 }
304 304
305 // 2. 更新合同审批结果 305 // 2. 更新合同审批结果
306 - Integer auditStatus = BpmProcessInstanceResultEnum.APPROVE.getResult().equals(bpmResult) ? CrmAuditStatusEnum.APPROVE.getStatus()  
307 - : BpmProcessInstanceResultEnum.REJECT.getResult().equals(bpmResult) ? CrmAuditStatusEnum.REJECT.getStatus()  
308 - : BpmProcessInstanceResultEnum.CANCEL.getResult();  
309 - Assert.notNull(auditStatus, "BPM 审批结果({}) 转换失败", bpmResult); 306 + Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
310 contractMapper.updateById(new CrmContractDO().setId(id).setAuditStatus(auditStatus)); 307 contractMapper.updateById(new CrmContractDO().setId(id).setAuditStatus(auditStatus));
311 } 308 }
312 309
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java
1 package cn.iocoder.yudao.module.crm.service.receivable; 1 package cn.iocoder.yudao.module.crm.service.receivable;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;  
5 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; 4 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
6 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; 5 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO;
7 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 6 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
8 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; 7 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
9 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
@@ -24,14 +23,22 @@ public interface CrmReceivablePlanService { @@ -24,14 +23,22 @@ public interface CrmReceivablePlanService {
24 * @param createReqVO 创建信息 23 * @param createReqVO 创建信息
25 * @return 编号 24 * @return 编号
26 */ 25 */
27 - Long createReceivablePlan(@Valid CrmReceivablePlanCreateReqVO createReqVO, Long userId); 26 + Long createReceivablePlan(@Valid CrmReceivablePlanSaveReqVO createReqVO);
28 27
29 /** 28 /**
30 * 更新回款计划 29 * 更新回款计划
31 * 30 *
32 * @param updateReqVO 更新信息 31 * @param updateReqVO 更新信息
33 */ 32 */
34 - void updateReceivablePlan(@Valid CrmReceivablePlanUpdateReqVO updateReqVO); 33 + void updateReceivablePlan(@Valid CrmReceivablePlanSaveReqVO updateReqVO);
  34 +
  35 + /**
  36 + * 更新回款计划关联的回款编号
  37 + *
  38 + * @param id 编号
  39 + * @param receivableId 回款编号
  40 + */
  41 + void updateReceivableId(Long id, Long receivableId);
35 42
36 /** 43 /**
37 * 删除回款计划 44 * 删除回款计划
@@ -84,4 +91,5 @@ public interface CrmReceivablePlanService { @@ -84,4 +91,5 @@ public interface CrmReceivablePlanService {
84 * @return 提醒数量 91 * @return 提醒数量
85 */ 92 */
86 Long getRemindReceivablePlanCount(Long userId); 93 Long getRemindReceivablePlanCount(Long userId);
  94 +
87 } 95 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
@@ -5,10 +5,8 @@ import cn.hutool.core.collection.ListUtil; @@ -5,10 +5,8 @@ import cn.hutool.core.collection.ListUtil;
5 import cn.hutool.core.util.ObjectUtil; 5 import cn.hutool.core.util.ObjectUtil;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;  
9 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; 8 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
10 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO;  
11 -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert; 9 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO;
12 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 10 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
13 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 11 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
14 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; 12 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
@@ -20,22 +18,24 @@ import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; @@ -20,22 +18,24 @@ import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
20 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; 18 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
21 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; 19 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
22 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; 20 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
  21 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
23 import com.mzt.logapi.context.LogRecordContext; 22 import com.mzt.logapi.context.LogRecordContext;
24 import com.mzt.logapi.service.impl.DiffParseFunction; 23 import com.mzt.logapi.service.impl.DiffParseFunction;
25 import com.mzt.logapi.starter.annotation.LogRecord; 24 import com.mzt.logapi.starter.annotation.LogRecord;
26 import jakarta.annotation.Resource; 25 import jakarta.annotation.Resource;
  26 +import org.hibernate.validator.internal.util.stereotypes.Lazy;
27 import org.springframework.stereotype.Service; 27 import org.springframework.stereotype.Service;
  28 +import org.springframework.transaction.annotation.Transactional;
28 import org.springframework.validation.annotation.Validated; 29 import org.springframework.validation.annotation.Validated;
29 30
30 import java.util.Collection; 31 import java.util.Collection;
31 import java.util.List; 32 import java.util.List;
  33 +import java.util.Objects;
32 34
33 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
34 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; 36 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
35 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; 37 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
36 38
37 -// TODO @liuhongfeng:参考 CrmReceivableServiceImpl 写的 todo 哈;  
38 -  
39 /** 39 /**
40 * 回款计划 Service 实现类 40 * 回款计划 Service 实现类
41 * 41 *
@@ -49,72 +49,86 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { @@ -49,72 +49,86 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
49 private CrmReceivablePlanMapper receivablePlanMapper; 49 private CrmReceivablePlanMapper receivablePlanMapper;
50 50
51 @Resource 51 @Resource
  52 + @Lazy // 延迟加载,避免循环依赖
  53 + private CrmReceivableService receivableService;
  54 + @Resource
52 private CrmContractService contractService; 55 private CrmContractService contractService;
53 @Resource 56 @Resource
54 private CrmCustomerService customerService; 57 private CrmCustomerService customerService;
55 @Resource 58 @Resource
56 private CrmPermissionService permissionService; 59 private CrmPermissionService permissionService;
  60 + @Resource
  61 + private AdminUserApi adminUserApi;
57 62
58 @Override 63 @Override
  64 + @Transactional(rollbackFor = Exception.class)
59 @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE, bizNo = "{{#receivablePlan.id}}", 65 @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE, bizNo = "{{#receivablePlan.id}}",
60 success = CRM_RECEIVABLE_PLAN_CREATE_SUCCESS) 66 success = CRM_RECEIVABLE_PLAN_CREATE_SUCCESS)
61 - public Long createReceivablePlan(CrmReceivablePlanCreateReqVO createReqVO, Long userId) {  
62 - // TODO @liuhongfeng:第几期的计算;基于是 contractId + contractDO 的第几个还款  
63 - // TODO @liuhongfeng contractId:校验合同是否存在  
64 - // 插入  
65 - CrmReceivablePlanDO receivablePlan = CrmReceivablePlanConvert.INSTANCE.convert(createReqVO);  
66 - receivablePlan.setFinishStatus(false);  
67 -  
68 - checkReceivablePlan(receivablePlan);  
69 - 67 + public Long createReceivablePlan(CrmReceivablePlanSaveReqVO createReqVO) {
  68 + // 1.1 校验关联数据是否存在
  69 + validateRelationDataExists(createReqVO);
  70 + // 1.2 查验关联合同回款数量
  71 + Long count = receivableService.getReceivableCountByContractId(createReqVO.getContractId());
  72 + int period = (int) (count + 1);
  73 + createReqVO.setPeriod(createReqVO.getPeriod() != period ? period : createReqVO.getPeriod()); // 如果期数不对则纠正
  74 +
  75 + // 2.1 插入
  76 + CrmReceivablePlanDO receivablePlan = BeanUtils.toBean(createReqVO, CrmReceivablePlanDO.class).setId(null).setFinishStatus(false);
70 receivablePlanMapper.insert(receivablePlan); 77 receivablePlanMapper.insert(receivablePlan);
71 - // 创建数据权限  
72 - permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) 78 + // 2.2 创建数据权限
  79 + permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(createReqVO.getOwnerUserId())
73 .setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()).setBizId(receivablePlan.getId()) 80 .setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()).setBizId(receivablePlan.getId())
74 .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); 81 .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
75 82
76 - // 4. 记录操作日志上下文 83 + // 3. 记录操作日志上下文
77 LogRecordContext.putVariable("receivablePlan", receivablePlan); 84 LogRecordContext.putVariable("receivablePlan", receivablePlan);
78 return receivablePlan.getId(); 85 return receivablePlan.getId();
79 } 86 }
80 87
81 - private void checkReceivablePlan(CrmReceivablePlanDO receivablePlan) {  
82 -  
83 - if (ObjectUtil.isNull(receivablePlan.getContractId())) {  
84 - throw exception(CONTRACT_NOT_EXISTS);  
85 - }  
86 -  
87 - CrmContractDO contract = contractService.getContract(receivablePlan.getContractId());  
88 - if (ObjectUtil.isNull(contract)) {  
89 - throw exception(CONTRACT_NOT_EXISTS);  
90 - }  
91 -  
92 - CrmCustomerDO customer = customerService.getCustomer(receivablePlan.getCustomerId());  
93 - if (ObjectUtil.isNull(customer)) {  
94 - throw exception(CUSTOMER_NOT_EXISTS);  
95 - }  
96 -  
97 - }  
98 -  
99 @Override 88 @Override
  89 + @Transactional(rollbackFor = Exception.class)
100 @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", 90 @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
101 success = CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS) 91 success = CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS)
102 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) 92 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
103 - public void updateReceivablePlan(CrmReceivablePlanUpdateReqVO updateReqVO) {  
104 - // TODO @liuhongfeng:如果已经有对应的还款,则不允许编辑;  
105 - // 校验存在 93 + public void updateReceivablePlan(CrmReceivablePlanSaveReqVO updateReqVO) {
  94 + // 1. 校验存在
  95 + validateRelationDataExists(updateReqVO);
106 CrmReceivablePlanDO oldReceivablePlan = validateReceivablePlanExists(updateReqVO.getId()); 96 CrmReceivablePlanDO oldReceivablePlan = validateReceivablePlanExists(updateReqVO.getId());
  97 + if (Objects.nonNull(oldReceivablePlan.getReceivableId())) { // 如果已经有对应的还款,则不允许编辑;
  98 + throw exception(RECEIVABLE_PLAN_UPDATE_FAIL, "已经有对应的还款");
  99 + }
107 100
108 - // 更新  
109 - CrmReceivablePlanDO updateObj = CrmReceivablePlanConvert.INSTANCE.convert(updateReqVO); 101 + // 2. 更新
  102 + CrmReceivablePlanDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivablePlanDO.class);
110 receivablePlanMapper.updateById(updateObj); 103 receivablePlanMapper.updateById(updateObj);
111 104
112 // 3. 记录操作日志上下文 105 // 3. 记录操作日志上下文
113 - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanUpdateReqVO.class)); 106 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanSaveReqVO.class));
114 LogRecordContext.putVariable("receivablePlan", oldReceivablePlan); 107 LogRecordContext.putVariable("receivablePlan", oldReceivablePlan);
115 } 108 }
116 109
117 @Override 110 @Override
  111 + public void updateReceivableId(Long id, Long receivableId) {
  112 + // 校验存在
  113 + validateReceivablePlanExists(id);
  114 + // 更新回款计划
  115 + receivablePlanMapper.updateById(new CrmReceivablePlanDO().setReceivableId(receivableId).setFinishStatus(true));
  116 + }
  117 +
  118 + private void validateRelationDataExists(CrmReceivablePlanSaveReqVO reqVO) {
  119 + adminUserApi.validateUser(reqVO.getOwnerUserId()); // 校验负责人存在
  120 + CrmContractDO contract = contractService.getContract(reqVO.getContractId());
  121 + if (ObjectUtil.isNull(contract)) { // 合同不存在
  122 + throw exception(CONTRACT_NOT_EXISTS);
  123 + }
  124 + CrmCustomerDO customer = customerService.getCustomer(reqVO.getCustomerId());
  125 + if (ObjectUtil.isNull(customer)) { // 客户不存在
  126 + throw exception(CUSTOMER_NOT_EXISTS);
  127 + }
  128 + }
  129 +
  130 + @Override
  131 + @Transactional(rollbackFor = Exception.class)
118 @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE, bizNo = "{{#id}}", 132 @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE, bizNo = "{{#id}}",
119 success = CRM_RECEIVABLE_PLAN_DELETE_SUCCESS) 133 success = CRM_RECEIVABLE_PLAN_DELETE_SUCCESS)
120 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) 134 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java
1 package cn.iocoder.yudao.module.crm.service.receivable; 1 package cn.iocoder.yudao.module.crm.service.receivable;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;  
5 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; 4 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
6 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; 5 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO;
7 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 6 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
8 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; 7 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
9 import jakarta.validation.Valid; 8 import jakarta.validation.Valid;
@@ -22,17 +21,24 @@ public interface CrmReceivableService { @@ -22,17 +21,24 @@ public interface CrmReceivableService {
22 * 创建回款 21 * 创建回款
23 * 22 *
24 * @param createReqVO 创建信息 23 * @param createReqVO 创建信息
25 - * @param userId 用户编号  
26 * @return 编号 24 * @return 编号
27 */ 25 */
28 - Long createReceivable(@Valid CrmReceivableCreateReqVO createReqVO, Long userId); 26 + Long createReceivable(@Valid CrmReceivableSaveReqVO createReqVO);
29 27
30 /** 28 /**
31 * 更新回款 29 * 更新回款
32 * 30 *
33 * @param updateReqVO 更新信息 31 * @param updateReqVO 更新信息
34 */ 32 */
35 - void updateReceivable(@Valid CrmReceivableUpdateReqVO updateReqVO); 33 + void updateReceivable(@Valid CrmReceivableSaveReqVO updateReqVO);
  34 +
  35 + /**
  36 + * 更新回款流程审批结果
  37 + *
  38 + * @param id 回款编号
  39 + * @param bpmResult BPM 审批结果
  40 + */
  41 + void updateReceivableAuditStatus(Long id, Integer bpmResult);
36 42
37 /** 43 /**
38 * 删除回款 44 * 删除回款
@@ -42,6 +48,14 @@ public interface CrmReceivableService { @@ -42,6 +48,14 @@ public interface CrmReceivableService {
42 void deleteReceivable(Long id); 48 void deleteReceivable(Long id);
43 49
44 /** 50 /**
  51 + * 发起回款审批流程
  52 + *
  53 + * @param id 回款编号
  54 + * @param userId 用户编号
  55 + */
  56 + void submitReceivable(Long id, Long userId);
  57 +
  58 + /**
45 * 获得回款 59 * 获得回款
46 * 60 *
47 * @param id 编号 61 * @param id 编号
@@ -86,4 +100,12 @@ public interface CrmReceivableService { @@ -86,4 +100,12 @@ public interface CrmReceivableService {
86 */ 100 */
87 Long getCheckReceivablesCount(Long userId); 101 Long getCheckReceivablesCount(Long userId);
88 102
  103 + /**
  104 + * 根据合同编号,获取合同关联的回款数量
  105 + *
  106 + * @param contractId 合同编号
  107 + * @return 数量
  108 + */
  109 + Long getReceivableCountByContractId(Long contractId);
  110 +
89 } 111 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
@@ -2,14 +2,16 @@ package cn.iocoder.yudao.module.crm.service.receivable; @@ -2,14 +2,16 @@ package cn.iocoder.yudao.module.crm.service.receivable;
2 2
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.collection.ListUtil; 4 import cn.hutool.core.collection.ListUtil;
  5 +import cn.hutool.core.lang.Assert;
  6 +import cn.hutool.core.util.ObjUtil;
5 import cn.hutool.core.util.ObjectUtil; 7 import cn.hutool.core.util.ObjectUtil;
6 -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;  
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 9 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
9 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; 10 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
  11 +import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
  12 +import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
10 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; 13 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
11 -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO;  
12 -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert; 14 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO;
13 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; 15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
14 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
15 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; 17 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
@@ -23,19 +25,24 @@ import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; @@ -23,19 +25,24 @@ import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
23 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; 25 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
24 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; 26 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
25 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; 27 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
  28 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
26 import com.mzt.logapi.context.LogRecordContext; 29 import com.mzt.logapi.context.LogRecordContext;
27 import com.mzt.logapi.service.impl.DiffParseFunction; 30 import com.mzt.logapi.service.impl.DiffParseFunction;
28 import com.mzt.logapi.starter.annotation.LogRecord; 31 import com.mzt.logapi.starter.annotation.LogRecord;
29 import jakarta.annotation.Resource; 32 import jakarta.annotation.Resource;
  33 +import lombok.extern.slf4j.Slf4j;
30 import org.springframework.stereotype.Service; 34 import org.springframework.stereotype.Service;
  35 +import org.springframework.transaction.annotation.Transactional;
31 import org.springframework.validation.annotation.Validated; 36 import org.springframework.validation.annotation.Validated;
32 37
33 import java.util.Collection; 38 import java.util.Collection;
34 import java.util.List; 39 import java.util.List;
  40 +import java.util.Objects;
35 41
36 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
37 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; 43 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
38 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; 44 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
  45 +import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus;
39 46
40 /** 47 /**
41 * CRM 回款 Service 实现类 48 * CRM 回款 Service 实现类
@@ -44,8 +51,14 @@ import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; @@ -44,8 +51,14 @@ import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
44 */ 51 */
45 @Service 52 @Service
46 @Validated 53 @Validated
  54 +@Slf4j
47 public class CrmReceivableServiceImpl implements CrmReceivableService { 55 public class CrmReceivableServiceImpl implements CrmReceivableService {
48 56
  57 + /**
  58 + * BPM 回款审批流程标识
  59 + */
  60 + public static final String RECEIVABLE_APPROVE = "receivable-approve";
  61 +
49 @Resource 62 @Resource
50 private CrmReceivableMapper receivableMapper; 63 private CrmReceivableMapper receivableMapper;
51 64
@@ -57,51 +70,47 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -57,51 +70,47 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
57 private CrmReceivablePlanService receivablePlanService; 70 private CrmReceivablePlanService receivablePlanService;
58 @Resource 71 @Resource
59 private CrmPermissionService permissionService; 72 private CrmPermissionService permissionService;
  73 + @Resource
  74 + private AdminUserApi adminUserApi;
  75 + @Resource
  76 + private BpmProcessInstanceApi bpmProcessInstanceApi;
60 77
61 @Override 78 @Override
  79 + @Transactional(rollbackFor = Exception.class)
62 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}", 80 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}",
63 success = CRM_RECEIVABLE_CREATE_SUCCESS) 81 success = CRM_RECEIVABLE_CREATE_SUCCESS)
64 - public Long createReceivable(CrmReceivableCreateReqVO createReqVO, Long userId) {  
65 - // 插入还款  
66 - CrmReceivableDO receivable = CrmReceivableConvert.INSTANCE.convert(createReqVO);  
67 - if (ObjectUtil.isNull(receivable.getAuditStatus())) {  
68 - receivable.setAuditStatus(CommonStatusEnum.ENABLE.getStatus());  
69 - }  
70 - receivable.setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());  
71 -  
72 - // TODO @liuhongfeng:一般来说,逻辑的写法,是要先检查,后操作 db;所以,你这个 check 应该放到 CrmReceivableDO receivable 之前;  
73 - checkReceivable(receivable);  
74 - 82 + public Long createReceivable(CrmReceivableSaveReqVO createReqVO) {
  83 + // 1. 校验关联数据存在
  84 + validateRelationDataExists(createReqVO);
  85 + // 2. 插入还款
  86 + CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus());
75 receivableMapper.insert(receivable); 87 receivableMapper.insert(receivable);
76 // 3. 创建数据权限 88 // 3. 创建数据权限
77 permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType()) 89 permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType())
78 - .setBizId(receivable.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人  
79 - // TODO @liuhongfeng:需要更新关联的 plan  
80 -  
81 - // 4. 记录操作日志上下文 90 + .setBizId(receivable.getId()).setUserId(createReqVO.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
  91 + // 4. 更新关联的回款计划
  92 + if (Objects.nonNull(createReqVO.getPlanId())) {
  93 + receivablePlanService.updateReceivableId(receivable.getPlanId(), receivable.getId());
  94 + }
  95 + // 5. 记录操作日志上下文
82 LogRecordContext.putVariable("receivable", receivable); 96 LogRecordContext.putVariable("receivable", receivable);
83 return receivable.getId(); 97 return receivable.getId();
84 } 98 }
85 99
86 - // TODO @liuhongfeng:这里的括号要注意排版;  
87 - private void checkReceivable(CrmReceivableDO receivable) {  
88 - // TODO @liuhongfeng:校验 no 的唯一性  
89 - // TODO @liuhongfeng:这个放在参数校验合适  
90 - if (ObjectUtil.isNull(receivable.getContractId())) {  
91 - throw exception(CONTRACT_NOT_EXISTS);  
92 - }  
93 -  
94 - CrmContractDO contract = contractService.getContract(receivable.getContractId()); 100 + private void validateRelationDataExists(CrmReceivableSaveReqVO reqVO) {
  101 + adminUserApi.validateUser(reqVO.getOwnerUserId()); // 校验负责人存在
  102 + CrmContractDO contract = contractService.getContract(reqVO.getContractId());
95 if (ObjectUtil.isNull(contract)) { 103 if (ObjectUtil.isNull(contract)) {
96 throw exception(CONTRACT_NOT_EXISTS); 104 throw exception(CONTRACT_NOT_EXISTS);
97 } 105 }
98 -  
99 - CrmCustomerDO customer = customerService.getCustomer(receivable.getCustomerId()); 106 + CrmCustomerDO customer = customerService.getCustomer(reqVO.getCustomerId());
100 if (ObjectUtil.isNull(customer)) { 107 if (ObjectUtil.isNull(customer)) {
101 throw exception(CUSTOMER_NOT_EXISTS); 108 throw exception(CUSTOMER_NOT_EXISTS);
102 } 109 }
103 -  
104 - CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(receivable.getPlanId()); 110 + if (Objects.isNull(reqVO.getPlanId())) { // 没有回款计划编号则不校验
  111 + return;
  112 + }
  113 + CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(reqVO.getPlanId());
105 if (ObjectUtil.isNull(receivablePlan)) { 114 if (ObjectUtil.isNull(receivablePlan)) {
106 throw exception(RECEIVABLE_PLAN_NOT_EXISTS); 115 throw exception(RECEIVABLE_PLAN_NOT_EXISTS);
107 } 116 }
@@ -109,46 +118,92 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -109,46 +118,92 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
109 } 118 }
110 119
111 @Override 120 @Override
  121 + @Transactional(rollbackFor = Exception.class)
112 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", 122 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
113 success = CRM_RECEIVABLE_UPDATE_SUCCESS) 123 success = CRM_RECEIVABLE_UPDATE_SUCCESS)
114 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) 124 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
115 - public void updateReceivable(CrmReceivableUpdateReqVO updateReqVO) {  
116 - // 校验存在  
117 - CrmReceivableDO oldReceivable = validateReceivableExists(updateReqVO.getId());  
118 - // TODO @liuhongfeng:只有在草稿、审核中,可以提交修改 125 + public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) {
  126 + Assert.notNull(updateReqVO.getId(), "回款编号不能为空");
  127 + // 1.1 校验存在
  128 + CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId());
  129 + // 1.2 只有草稿、审批中,可以编辑;
  130 + if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(),
  131 + CrmAuditStatusEnum.PROCESS.getStatus())) {
  132 + throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED);
  133 + }
119 134
120 - // 更新还款  
121 - CrmReceivableDO updateObj = CrmReceivableConvert.INSTANCE.convert(updateReqVO); 135 + // 2. 更新还款
  136 + CrmReceivableDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivableDO.class);
122 receivableMapper.updateById(updateObj); 137 receivableMapper.updateById(updateObj);
123 138
124 - // TODO @liuhongfeng:需要更新关联的 plan  
125 // 3. 记录操作日志上下文 139 // 3. 记录操作日志上下文
126 - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivable, CrmReceivableUpdateReqVO.class));  
127 - LogRecordContext.putVariable("receivable", oldReceivable); 140 + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class));
  141 + LogRecordContext.putVariable("receivable", receivable);
128 } 142 }
129 143
130 - // TODO @liuhongfeng:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum 144 + @Override
  145 + public void updateReceivableAuditStatus(Long id, Integer bpmResult) {
  146 + // 1.1 校验存在
  147 + CrmReceivableDO receivable = validateReceivableExists(id);
  148 + // 1.2 只有审批中,可以更新审批结果
  149 + if (ObjUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
  150 + log.error("[updateReceivableAuditStatus][receivable({}) 不处于审批中,无法更新审批结果({})]",
  151 + receivable.getId(), bpmResult);
  152 + throw exception(RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
  153 + }
  154 +
  155 + // 2. 更新回款审批状态
  156 + Integer auditStatus = convertBpmResultToAuditStatus(bpmResult);
  157 + receivableMapper.updateById(new CrmReceivableDO().setId(id).setAuditStatus(auditStatus));
  158 + }
131 159
132 - // TODO @liuhongfeng:缺一个发起审批的接口;只有草稿可以发起审批;CrmAuditStatusEnum 160 + // TODO @liuhongfeng:缺一个取消回款的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum
133 161
134 @Override 162 @Override
  163 + @Transactional(rollbackFor = Exception.class)
135 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_DELETE_SUB_TYPE, bizNo = "{{#id}}", 164 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_DELETE_SUB_TYPE, bizNo = "{{#id}}",
136 success = CRM_RECEIVABLE_DELETE_SUCCESS) 165 success = CRM_RECEIVABLE_DELETE_SUCCESS)
137 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) 166 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
138 public void deleteReceivable(Long id) { 167 public void deleteReceivable(Long id) {
139 - // TODO @liuhongfeng:如果被 CrmReceivablePlanDO 所使用,则不允许删除  
140 // 校验存在 168 // 校验存在
141 CrmReceivableDO receivable = validateReceivableExists(id); 169 CrmReceivableDO receivable = validateReceivableExists(id);
  170 + // 如果被 CrmReceivablePlanDO 所使用,则不允许删除
  171 + if (Objects.nonNull(receivable.getPlanId()) && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) {
  172 + throw exception(RECEIVABLE_DELETE_FAIL);
  173 + }
142 // 删除 174 // 删除
143 receivableMapper.deleteById(id); 175 receivableMapper.deleteById(id);
144 176
145 // 删除数据权限 177 // 删除数据权限
146 - permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); 178 + permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id);
147 179
148 // 记录操作日志上下文 180 // 记录操作日志上下文
149 LogRecordContext.putVariable("receivable", receivable); 181 LogRecordContext.putVariable("receivable", receivable);
150 } 182 }
151 183
  184 + @Override
  185 + @Transactional(rollbackFor = Exception.class)
  186 + @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_SUBMIT_SUB_TYPE, bizNo = "{{#id}}",
  187 + success = CRM_RECEIVABLE_SUBMIT_SUCCESS)
  188 + public void submitReceivable(Long id, Long userId) {
  189 + // 1. 校验回款是否在审批
  190 + CrmReceivableDO receivable = validateReceivableExists(id);
  191 + if (ObjUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) {
  192 + throw exception(RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT);
  193 + }
  194 +
  195 + // 2. 创建回款审批流程实例
  196 + String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
  197 + .setProcessDefinitionKey(RECEIVABLE_APPROVE).setBusinessKey(String.valueOf(id)));
  198 +
  199 + // 3. 更新回款工作流编号
  200 + receivableMapper.updateById(new CrmReceivableDO().setId(id).setProcessInstanceId(processInstanceId)
  201 + .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
  202 +
  203 + // 4. 记录日志
  204 + LogRecordContext.putVariable("receivableNo", receivable.getNo());
  205 + }
  206 +
152 private CrmReceivableDO validateReceivableExists(Long id) { 207 private CrmReceivableDO validateReceivableExists(Long id) {
153 CrmReceivableDO receivable = receivableMapper.selectById(id); 208 CrmReceivableDO receivable = receivableMapper.selectById(id);
154 if (receivable == null) { 209 if (receivable == null) {
@@ -187,4 +242,9 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { @@ -187,4 +242,9 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
187 return receivableMapper.selectCheckReceivablesCount(userId); 242 return receivableMapper.selectCheckReceivablesCount(userId);
188 } 243 }
189 244
  245 + @Override
  246 + public Long getReceivableCountByContractId(Long contractId) {
  247 + return receivableMapper.selectCount(CrmReceivableDO::getContractId, contractId);
  248 + }
  249 +
190 } 250 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/listener/CrmReceivableResultListener.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.receivable.listener;
  2 +
  3 +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent;
  4 +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEventListener;
  5 +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
  6 +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableServiceImpl;
  7 +import jakarta.annotation.Resource;
  8 +import org.springframework.stereotype.Component;
  9 +
  10 +/**
  11 + * 回款审批的结果的监听器实现类
  12 + *
  13 + * @author HUIHUI
  14 + */
  15 +@Component
  16 +public class CrmReceivableResultListener extends BpmProcessInstanceResultEventListener {
  17 +
  18 + @Resource
  19 + private CrmReceivableService receivableService;
  20 +
  21 + @Override
  22 + public String getProcessDefinitionKey() {
  23 + return CrmReceivableServiceImpl.RECEIVABLE_APPROVE;
  24 + }
  25 +
  26 + @Override
  27 + public void onEvent(BpmProcessInstanceResultEvent event) {
  28 + receivableService.updateReceivableAuditStatus(Long.parseLong(event.getBusinessKey()), event.getResult());
  29 + }
  30 +
  31 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmAuditStatusUtils.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.util;
  2 +
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
  5 +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
  6 +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
  7 +
  8 +/**
  9 + * CRM 流程工具类
  10 + *
  11 + * @author HUIHUI
  12 + */
  13 +public class CrmAuditStatusUtils {
  14 +
  15 + /**
  16 + * BPM 审批结果转换
  17 + *
  18 + * @param bpmResult BPM 审批结果
  19 + */
  20 + public static Integer convertBpmResultToAuditStatus(Integer bpmResult) {
  21 + Assert.isTrue(isEndResult(bpmResult), "BPM 审批结果({}) 转换失败, 流程状态不是最终结果", bpmResult);
  22 + Integer auditStatus = BpmProcessInstanceResultEnum.APPROVE.getResult().equals(bpmResult) ? CrmAuditStatusEnum.APPROVE.getStatus()
  23 + : BpmProcessInstanceResultEnum.REJECT.getResult().equals(bpmResult) ? CrmAuditStatusEnum.REJECT.getStatus()
  24 + : BpmProcessInstanceResultEnum.CANCEL.getResult();
  25 + Assert.notNull(auditStatus, "BPM 审批结果({}) 转换失败", bpmResult);
  26 + return auditStatus;
  27 + }
  28 +
  29 + /**
  30 + * 判断该结果是否处于 End 最终结果
  31 + *
  32 + * @param bpmResult BPM 审批结果
  33 + * @return 是否
  34 + */
  35 + public static boolean isEndResult(Integer bpmResult) {
  36 + return ObjectUtils.equalsAny(bpmResult, BpmProcessInstanceResultEnum.APPROVE.getResult(),
  37 + BpmProcessInstanceResultEnum.REJECT.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult());
  38 + }
  39 +
  40 +}
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java
@@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.api.dict; @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.api.dict;
3 import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; 3 import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
4 4
5 import java.util.Collection; 5 import java.util.Collection;
  6 +import java.util.List;
6 7
7 /** 8 /**
8 * 字典数据 API 接口 9 * 字典数据 API 接口
@@ -39,4 +40,12 @@ public interface DictDataApi { @@ -39,4 +40,12 @@ public interface DictDataApi {
39 */ 40 */
40 DictDataRespDTO parseDictData(String type, String label); 41 DictDataRespDTO parseDictData(String type, String label);
41 42
  43 + /**
  44 + * 获得字典数据标签列表
  45 + *
  46 + * @param dictType 字典类型
  47 + * @return 字典数据标签列表
  48 + */
  49 + List<String> getDictDataLabelList(String dictType);
  50 +
42 } 51 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApiImpl.java
1 package cn.iocoder.yudao.module.system.api.dict; 1 package cn.iocoder.yudao.module.system.api.dict;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; 5 import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
5 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO; 6 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
6 import cn.iocoder.yudao.module.system.service.dict.DictDataService; 7 import cn.iocoder.yudao.module.system.service.dict.DictDataService;
  8 +import jakarta.annotation.Resource;
7 import org.springframework.stereotype.Service; 9 import org.springframework.stereotype.Service;
8 10
9 -import jakarta.annotation.Resource;  
10 import java.util.Collection; 11 import java.util.Collection;
  12 +import java.util.Collections;
  13 +import java.util.List;
  14 +
  15 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
11 16
12 /** 17 /**
13 * 字典数据 API 实现类 18 * 字典数据 API 实现类
@@ -37,4 +42,13 @@ public class DictDataApiImpl implements DictDataApi { @@ -37,4 +42,13 @@ public class DictDataApiImpl implements DictDataApi {
37 return BeanUtils.toBean(dictData, DictDataRespDTO.class); 42 return BeanUtils.toBean(dictData, DictDataRespDTO.class);
38 } 43 }
39 44
  45 + @Override
  46 + public List<String> getDictDataLabelList(String dictType) {
  47 + List<DictDataDO> dictDataList = dictDataService.getDictDataListByDictType(dictType);
  48 + if (CollUtil.isEmpty(dictDataList)) {
  49 + return Collections.emptyList();
  50 + }
  51 + return convertList(dictDataList, DictDataDO::getLabel);
  52 + }
  53 +
40 } 54 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java
@@ -99,4 +99,12 @@ public interface DictDataService { @@ -99,4 +99,12 @@ public interface DictDataService {
99 */ 99 */
100 DictDataDO parseDictData(String dictType, String label); 100 DictDataDO parseDictData(String dictType, String label);
101 101
  102 + /**
  103 + * 获得字典数据列表
  104 + *
  105 + * @param dictType 字典类型
  106 + * @return 字典数据列表
  107 + */
  108 + List<DictDataDO> getDictDataListByDictType(String dictType);
  109 +
102 } 110 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java
@@ -11,10 +11,10 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO; @@ -11,10 +11,10 @@ import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
11 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO; 11 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO;
12 import cn.iocoder.yudao.module.system.dal.mysql.dict.DictDataMapper; 12 import cn.iocoder.yudao.module.system.dal.mysql.dict.DictDataMapper;
13 import com.google.common.annotations.VisibleForTesting; 13 import com.google.common.annotations.VisibleForTesting;
  14 +import jakarta.annotation.Resource;
14 import lombok.extern.slf4j.Slf4j; 15 import lombok.extern.slf4j.Slf4j;
15 import org.springframework.stereotype.Service; 16 import org.springframework.stereotype.Service;
16 17
17 -import jakarta.annotation.Resource;  
18 import java.util.Collection; 18 import java.util.Collection;
19 import java.util.Comparator; 19 import java.util.Comparator;
20 import java.util.List; 20 import java.util.List;
@@ -169,4 +169,9 @@ public class DictDataServiceImpl implements DictDataService { @@ -169,4 +169,9 @@ public class DictDataServiceImpl implements DictDataService {
169 return dictDataMapper.selectByDictTypeAndLabel(dictType, label); 169 return dictDataMapper.selectByDictTypeAndLabel(dictType, label);
170 } 170 }
171 171
  172 + @Override
  173 + public List<DictDataDO> getDictDataListByDictType(String dictType) {
  174 + return dictDataMapper.selectList(DictDataDO::getDictType, dictType);
  175 + }
  176 +
172 } 177 }