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 4 import cn.hutool.core.lang.Assert;
5 5 import cn.hutool.core.text.csv.CsvRow;
6 6 import cn.hutool.core.text.csv.CsvUtil;
  7 +import cn.hutool.core.util.ObjUtil;
7 8 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
8 9 import cn.iocoder.yudao.framework.ip.core.Area;
9 10 import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
... ... @@ -17,6 +18,7 @@ import java.util.Map;
17 18 import java.util.function.Function;
18 19  
19 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 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 134 * @param id 区域编号
... ... @@ -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 152 * @param separator 分隔符
99 153 * @return 格式化后的区域
100 154 */
... ...
yudao-framework/yudao-spring-boot-starter-excel/pom.xml
... ... @@ -46,6 +46,11 @@
46 46 <groupId>com.alibaba</groupId>
47 47 <artifactId>easyexcel</artifactId>
48 48 </dependency>
  49 + <dependency>
  50 + <groupId>cn.iocoder.boot</groupId>
  51 + <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
  52 + <scope>provided</scope> <!-- 设置为 provided,只有 ExcelUtils 使用 -->
  53 + </dependency>
49 54 </dependencies>
50 55  
51 56 </project>
... ...
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.excel.core.convert;
  2 +
  3 +import cn.hutool.core.convert.Convert;
  4 +import cn.iocoder.yudao.framework.ip.core.Area;
  5 +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
  6 +import com.alibaba.excel.converters.Converter;
  7 +import com.alibaba.excel.enums.CellDataTypeEnum;
  8 +import com.alibaba.excel.metadata.GlobalConfiguration;
  9 +import com.alibaba.excel.metadata.data.ReadCellData;
  10 +import com.alibaba.excel.metadata.property.ExcelContentProperty;
  11 +import lombok.extern.slf4j.Slf4j;
  12 +
  13 +/**
  14 + * Excel 数据地区转换器
  15 + *
  16 + * @author HUIHUI
  17 + */
  18 +@Slf4j
  19 +public class AreaConvert implements Converter<Object> {
  20 +
  21 + @Override
  22 + public Class<?> supportJavaTypeKey() {
  23 + throw new UnsupportedOperationException("暂不支持,也不需要");
  24 + }
  25 +
  26 + @Override
  27 + public CellDataTypeEnum supportExcelTypeKey() {
  28 + throw new UnsupportedOperationException("暂不支持,也不需要");
  29 + }
  30 +
  31 + @Override
  32 + public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty,
  33 + GlobalConfiguration globalConfiguration) {
  34 + // 解析地区编号
  35 + String label = readCellData.getStringValue();
  36 + Area area = AreaUtils.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 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 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 5 import com.alibaba.excel.EasyExcel;
4 6 import com.alibaba.excel.converters.longconverter.LongStringConverter;
5 7 import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
  8 +import jakarta.servlet.http.HttpServletResponse;
6 9 import org.springframework.web.multipart.MultipartFile;
7 10  
8   -import jakarta.servlet.http.HttpServletResponse;
9 11 import java.io.IOException;
10 12 import java.net.URLEncoder;
11 13 import java.nio.charset.StandardCharsets;
... ... @@ -21,12 +23,12 @@ public class ExcelUtils {
21 23 /**
22 24 * 将列表以 Excel 响应给前端
23 25 *
24   - * @param response 响应
25   - * @param filename 文件名
  26 + * @param response 响应
  27 + * @param filename 文件名
26 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 32 * @throws IOException 写入失败的情况
31 33 */
32 34 public static <T> void write(HttpServletResponse response, String filename, String sheetName,
... ... @@ -38,12 +40,37 @@ public class ExcelUtils {
38 40 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度
39 41 .sheet(sheetName).doWrite(data);
40 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 69 response.setContentType("application/vnd.ms-excel;charset=UTF-8");
43 70 }
44 71  
45 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 74 .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理
48 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 13 ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在");
14 14 ErrorCode CONTRACT_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_001, "合同更新失败,原因:合同不是草稿状态");
15 15 ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态");
16   - ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是处理中状态");
  16 + ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态");
17 17  
18 18 // ========== 线索管理 1-020-001-000 ==========
19 19 ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
... ... @@ -33,9 +33,14 @@ public interface ErrorCodeConstants {
33 33  
34 34 // ========== 回款 1-020-004-000 ==========
35 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 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 45 // ========== 客户管理 1_020_006_000 ==========
41 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 141 String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}";
142 142 String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款";
143 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 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 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.map.MapUtil;
  5 +import cn.iocoder.yudao.framework.common.core.KeyValue;
5 6 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 7 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 8 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
... ... @@ -10,6 +11,7 @@ import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
10 11 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
11 12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
12 13 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  14 +import cn.iocoder.yudao.framework.ip.core.Area;
13 15 import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
14 16 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
15 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 21 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
20 22 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
21 23 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
  24 +import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
22 25 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
23 26 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
24 27 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -34,6 +37,7 @@ import org.springframework.web.bind.annotation.*;
34 37  
35 38 import java.io.IOException;
36 39 import java.time.LocalDateTime;
  40 +import java.util.ArrayList;
37 41 import java.util.Arrays;
38 42 import java.util.List;
39 43 import java.util.Map;
... ... @@ -44,6 +48,7 @@ import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE;
44 48 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
45 49 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
46 50 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
  51 +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;
47 52 import static java.util.Collections.singletonList;
48 53  
49 54 @Tag(name = "管理后台 - CRM 客户")
... ... @@ -61,6 +66,8 @@ public class CrmCustomerController {
61 66 private DeptApi deptApi;
62 67 @Resource
63 68 private AdminUserApi adminUserApi;
  69 + @Resource
  70 + private DictDataApi dictDataApi;
64 71  
65 72 @PostMapping("/create")
66 73 @Operation(summary = "创建客户")
... ... @@ -258,7 +265,7 @@ public class CrmCustomerController {
258 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 271 @PostMapping("/import")
... ... @@ -314,4 +321,21 @@ public class CrmCustomerController {
314 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 1 package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer;
2 2  
3 3 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert;
4 5 import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
5 6 import com.alibaba.excel.annotation.ExcelProperty;
6 7 import lombok.AllArgsConstructor;
... ... @@ -39,14 +40,12 @@ public class CrmCustomerImportExcelVO {
39 40 @ExcelProperty("邮箱")
40 41 private String email;
41 42  
42   - // TODO @puhui999:需要选择省市区,需要研究下,怎么搞合理点;
43   - @ExcelProperty("地区编号")
  43 + @ExcelProperty(value = "地区", converter = AreaConvert.class)
44 44 private Integer areaId;
45 45  
46 46 @ExcelProperty("详细地址")
47 47 private String detailAddress;
48 48  
49   - // TODO @puhui999:industryId、level、source 字段,可以研究下怎么搞下拉框
50 49 @ExcelProperty(value = "所属行业", converter = DictConvert.class)
51 50 @DictFormat(CRM_CUSTOMER_INDUSTRY)
52 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 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;
11 11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO;
12 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 14 import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert;
15 15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
... ... @@ -61,14 +61,14 @@ public class CrmReceivableController {
61 61 @PostMapping("/create")
62 62 @Operation(summary = "创建回款")
63 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 68 @PutMapping("/update")
69 69 @Operation(summary = "更新回款")
70 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 72 receivableService.updateReceivable(updateReqVO);
73 73 return success(true);
74 74 }
... ... @@ -88,7 +88,7 @@ public class CrmReceivableController {
88 88 @PreAuthorize("@ss.hasPermission('crm:receivable:query')")
89 89 public CommonResult<CrmReceivableRespVO> getReceivable(@RequestParam("id") Long id) {
90 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 94 @GetMapping("/page")
... ... @@ -152,4 +152,12 @@ public class CrmReceivableController {
152 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 5 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7 import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8 9 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
9 10 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
10   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
11 11 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO;
12 12 import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO;
13   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO;
  13 +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO;
14 14 import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert;
15 15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
16 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
... ... @@ -67,14 +67,14 @@ public class CrmReceivablePlanController {
67 67 @PostMapping("/create")
68 68 @Operation(summary = "创建回款计划")
69 69 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:create')")
70   - public CommonResult<Long> createReceivablePlan(@Valid @RequestBody CrmReceivablePlanCreateReqVO createReqVO) {
71   - return success(receivablePlanService.createReceivablePlan(createReqVO, getLoginUserId()));
  70 + public CommonResult<Long> createReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO createReqVO) {
  71 + return success(receivablePlanService.createReceivablePlan(createReqVO));
72 72 }
73 73  
74 74 @PutMapping("/update")
75 75 @Operation(summary = "更新回款计划")
76 76 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:update')")
77   - public CommonResult<Boolean> updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanUpdateReqVO updateReqVO) {
  77 + public CommonResult<Boolean> updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO updateReqVO) {
78 78 receivablePlanService.updateReceivablePlan(updateReqVO);
79 79 return success(true);
80 80 }
... ... @@ -94,7 +94,7 @@ public class CrmReceivablePlanController {
94 94 @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')")
95 95 public CommonResult<CrmReceivablePlanRespVO> getReceivablePlan(@RequestParam("id") Long id) {
96 96 CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(id);
97   - return success(CrmReceivablePlanConvert.INSTANCE.convert(receivablePlan));
  97 + return success(BeanUtils.toBean(receivablePlan, CrmReceivablePlanRespVO.class));
98 98 }
99 99  
100 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 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.*;
  4 +import lombok.Data;
  5 +
  6 +import java.math.BigDecimal;
5 7 import java.time.LocalDateTime;
6 8  
7 9 @Schema(description = "管理后台 - CRM 回款计划 Response VO")
8 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 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 49 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
17 50 private LocalDateTime createTime;
18 51  
... ... @@ -34,7 +67,7 @@ public class CrmReceivablePlanRespVO extends CrmReceivablePlanBaseVO {
34 67 @Schema(description = "完成状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
35 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 1 package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4   -import lombok.*;
  4 +import lombok.Data;
  5 +
  6 +import java.math.BigDecimal;
5 7 import java.time.LocalDateTime;
6 8  
7 9 // TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能
8 10 @Schema(description = "管理后台 - CRM 回款 Response VO")
9 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 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 47 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
18 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 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;
6 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 6 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
9 7 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
10 8 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
... ... @@ -28,12 +26,6 @@ public interface CrmReceivableConvert {
28 26  
29 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 29 default PageResult<CrmReceivableRespVO> convertPage(PageResult<CrmReceivableDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
38 30 List<CrmCustomerDO> customerList, List<CrmContractDO> contractList) {
39 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 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
6 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 6 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
9 7 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
10 8 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
... ... @@ -29,12 +27,6 @@ public interface CrmReceivablePlanConvert {
29 27  
30 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 30 default PageResult<CrmReceivablePlanRespVO> convertPage(PageResult<CrmReceivablePlanDO> pageResult, Map<Long, AdminUserRespDTO> userMap,
39 31 List<CrmCustomerDO> customerList, List<CrmContractDO> contractList,
40 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 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 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 9 import com.baomidou.mybatisplus.annotation.KeySequence;
6 10 import com.baomidou.mybatisplus.annotation.TableId;
7 11 import com.baomidou.mybatisplus.annotation.TableName;
8 12 import lombok.*;
9 13  
  14 +import java.math.BigDecimal;
10 15 import java.time.LocalDateTime;
11 16  
12 17 /**
... ... @@ -33,64 +38,55 @@ public class CrmReceivableDO extends BaseDO {
33 38 * 回款编号
34 39 */
35 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 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 48 private Long customerId;
51 49 /**
52   - * 合同 ID
53   - *
54   - * 对应实体 {@link CrmContractDO}
  50 + * 合同编号,关联 {@link CrmContractDO#getId()}
55 51 */
56 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 61 private LocalDateTime returnTime;
67   - // TODO @liuhongfeng:少个枚举
68 62 /**
69   - * 回款方式
  63 + * 回款方式,关联枚举{@link CrmReturnTypeEnum}
70 64 */
71 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 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 1 package cn.iocoder.yudao.module.crm.dal.dataobject.receivable;
2 2  
3 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 7 import com.baomidou.mybatisplus.annotation.KeySequence;
5 8 import com.baomidou.mybatisplus.annotation.TableId;
6 9 import com.baomidou.mybatisplus.annotation.TableName;
7 10 import lombok.*;
8 11  
  12 +import java.math.BigDecimal;
9 13 import java.time.LocalDateTime;
10 14  
11 15 /**
... ... @@ -24,7 +28,7 @@ import java.time.LocalDateTime;
24 28 public class CrmReceivablePlanDO extends BaseDO {
25 29  
26 30 /**
27   - * ID
  31 + * 编号
28 32 */
29 33 @TableId
30 34 private Long id;
... ... @@ -33,19 +37,13 @@ public class CrmReceivablePlanDO extends BaseDO {
33 37 */
34 38 private Integer period;
35 39 /**
36   - * 回款ID
37   - *
38   - * TODO @liuhongfeng:少关联实体;
  40 + * 回款编号,关联 {@link CrmReceivableDO#getId()}
39 41 */
40 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 57 */
60 58 private LocalDateTime remindTime;
61 59 /**
62   - * 客户 ID
63   - *
64   - * TODO @liuhongfeng:少关联实体;
  60 + * 客户编号,关联 {@link CrmCustomerDO#getId()}
65 61 */
66 62 private Long customerId;
67 63 /**
68   - * 合同 ID
69   - *
70   - * TODO @liuhongfeng:少关联实体;
  64 + * 合同编号,关联 {@link CrmContractDO#getId()}
71 65 */
72 66 private Long contractId;
73 67 /**
74   - * 负责人 ID
75   - *
76   - * TODO @liuhongfeng:少关联实体;
  68 + * 负责人编号,关联 {@link AdminUserRespDTO#getId()}
77 69 */
78 70 private Long ownerUserId;
79 71 /**
... ... @@ -85,4 +77,9 @@ public class CrmReceivablePlanDO extends BaseDO {
85 77 */
86 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 10 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
11 11 import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum;
12 12 import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils;
13   -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
14 13 import org.apache.ibatis.annotations.Mapper;
15 14  
16 15 import java.time.LocalDateTime;
... ... @@ -25,12 +24,6 @@ import java.util.List;
25 24 @Mapper
26 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 27 default PageResult<CrmReceivablePlanDO> selectPageByCustomerId(CrmReceivablePlanPageReqVO reqVO) {
35 28 return selectPage(reqVO, new LambdaQueryWrapperX<CrmReceivablePlanDO>()
36 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 10 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
11 11 import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
12 12 import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
13   -import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum;
14 13 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
15 14 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO;
16 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 47 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
49 48 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
50 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 53 * CRM 合同 Service 实现类
... ... @@ -303,10 +303,7 @@ public class CrmContractServiceImpl implements CrmContractService {
303 303 }
304 304  
305 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 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 1 package cn.iocoder.yudao.module.crm.service.receivable;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
5 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 6 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
8 7 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO;
9 8 import jakarta.validation.Valid;
... ... @@ -24,14 +23,22 @@ public interface CrmReceivablePlanService {
24 23 * @param createReqVO 创建信息
25 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 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 91 * @return 提醒数量
85 92 */
86 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 5 import cn.hutool.core.util.ObjectUtil;
6 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
8   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO;
9 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 10 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
13 11 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
14 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 18 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
21 19 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
22 20 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
  21 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
23 22 import com.mzt.logapi.context.LogRecordContext;
24 23 import com.mzt.logapi.service.impl.DiffParseFunction;
25 24 import com.mzt.logapi.starter.annotation.LogRecord;
26 25 import jakarta.annotation.Resource;
  26 +import org.hibernate.validator.internal.util.stereotypes.Lazy;
27 27 import org.springframework.stereotype.Service;
  28 +import org.springframework.transaction.annotation.Transactional;
28 29 import org.springframework.validation.annotation.Validated;
29 30  
30 31 import java.util.Collection;
31 32 import java.util.List;
  33 +import java.util.Objects;
32 34  
33 35 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
34 36 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
35 37 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
36 38  
37   -// TODO @liuhongfeng:参考 CrmReceivableServiceImpl 写的 todo 哈;
38   -
39 39 /**
40 40 * 回款计划 Service 实现类
41 41 *
... ... @@ -49,72 +49,86 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService {
49 49 private CrmReceivablePlanMapper receivablePlanMapper;
50 50  
51 51 @Resource
  52 + @Lazy // 延迟加载,避免循环依赖
  53 + private CrmReceivableService receivableService;
  54 + @Resource
52 55 private CrmContractService contractService;
53 56 @Resource
54 57 private CrmCustomerService customerService;
55 58 @Resource
56 59 private CrmPermissionService permissionService;
  60 + @Resource
  61 + private AdminUserApi adminUserApi;
57 62  
58 63 @Override
  64 + @Transactional(rollbackFor = Exception.class)
59 65 @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE, bizNo = "{{#receivablePlan.id}}",
60 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 77 receivablePlanMapper.insert(receivablePlan);
71   - // 创建数据权限
72   - permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId)
  78 + // 2.2 创建数据权限
  79 + permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(createReqVO.getOwnerUserId())
73 80 .setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()).setBizId(receivablePlan.getId())
74 81 .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
75 82  
76   - // 4. 记录操作日志上下文
  83 + // 3. 记录操作日志上下文
77 84 LogRecordContext.putVariable("receivablePlan", receivablePlan);
78 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 88 @Override
  89 + @Transactional(rollbackFor = Exception.class)
100 90 @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
101 91 success = CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS)
102 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 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 103 receivablePlanMapper.updateById(updateObj);
111 104  
112 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 107 LogRecordContext.putVariable("receivablePlan", oldReceivablePlan);
115 108 }
116 109  
117 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 132 @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE, bizNo = "{{#id}}",
119 133 success = CRM_RECEIVABLE_PLAN_DELETE_SUCCESS)
120 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 1 package cn.iocoder.yudao.module.crm.service.receivable;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO;
5 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 6 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
8 7 import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO;
9 8 import jakarta.validation.Valid;
... ... @@ -22,17 +21,24 @@ public interface CrmReceivableService {
22 21 * 创建回款
23 22 *
24 23 * @param createReqVO 创建信息
25   - * @param userId 用户编号
26 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 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 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 61 * @param id 编号
... ... @@ -86,4 +100,12 @@ public interface CrmReceivableService {
86 100 */
87 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 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.ListUtil;
  5 +import cn.hutool.core.lang.Assert;
  6 +import cn.hutool.core.util.ObjUtil;
5 7 import cn.hutool.core.util.ObjectUtil;
6   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
7 8 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 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 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 15 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
14 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
15 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 25 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
24 26 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
25 27 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
  28 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
26 29 import com.mzt.logapi.context.LogRecordContext;
27 30 import com.mzt.logapi.service.impl.DiffParseFunction;
28 31 import com.mzt.logapi.starter.annotation.LogRecord;
29 32 import jakarta.annotation.Resource;
  33 +import lombok.extern.slf4j.Slf4j;
30 34 import org.springframework.stereotype.Service;
  35 +import org.springframework.transaction.annotation.Transactional;
31 36 import org.springframework.validation.annotation.Validated;
32 37  
33 38 import java.util.Collection;
34 39 import java.util.List;
  40 +import java.util.Objects;
35 41  
36 42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
37 43 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
38 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 48 * CRM 回款 Service 实现类
... ... @@ -44,8 +51,14 @@ import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
44 51 */
45 52 @Service
46 53 @Validated
  54 +@Slf4j
47 55 public class CrmReceivableServiceImpl implements CrmReceivableService {
48 56  
  57 + /**
  58 + * BPM 回款审批流程标识
  59 + */
  60 + public static final String RECEIVABLE_APPROVE = "receivable-approve";
  61 +
49 62 @Resource
50 63 private CrmReceivableMapper receivableMapper;
51 64  
... ... @@ -57,51 +70,47 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
57 70 private CrmReceivablePlanService receivablePlanService;
58 71 @Resource
59 72 private CrmPermissionService permissionService;
  73 + @Resource
  74 + private AdminUserApi adminUserApi;
  75 + @Resource
  76 + private BpmProcessInstanceApi bpmProcessInstanceApi;
60 77  
61 78 @Override
  79 + @Transactional(rollbackFor = Exception.class)
62 80 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}",
63 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 87 receivableMapper.insert(receivable);
76 88 // 3. 创建数据权限
77 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 96 LogRecordContext.putVariable("receivable", receivable);
83 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 103 if (ObjectUtil.isNull(contract)) {
96 104 throw exception(CONTRACT_NOT_EXISTS);
97 105 }
98   -
99   - CrmCustomerDO customer = customerService.getCustomer(receivable.getCustomerId());
  106 + CrmCustomerDO customer = customerService.getCustomer(reqVO.getCustomerId());
100 107 if (ObjectUtil.isNull(customer)) {
101 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 114 if (ObjectUtil.isNull(receivablePlan)) {
106 115 throw exception(RECEIVABLE_PLAN_NOT_EXISTS);
107 116 }
... ... @@ -109,46 +118,92 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
109 118 }
110 119  
111 120 @Override
  121 + @Transactional(rollbackFor = Exception.class)
112 122 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
113 123 success = CRM_RECEIVABLE_UPDATE_SUCCESS)
114 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 137 receivableMapper.updateById(updateObj);
123 138  
124   - // TODO @liuhongfeng:需要更新关联的 plan
125 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 162 @Override
  163 + @Transactional(rollbackFor = Exception.class)
135 164 @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_DELETE_SUB_TYPE, bizNo = "{{#id}}",
136 165 success = CRM_RECEIVABLE_DELETE_SUCCESS)
137 166 @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER)
138 167 public void deleteReceivable(Long id) {
139   - // TODO @liuhongfeng:如果被 CrmReceivablePlanDO 所使用,则不允许删除
140 168 // 校验存在
141 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 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 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 207 private CrmReceivableDO validateReceivableExists(Long id) {
153 208 CrmReceivableDO receivable = receivableMapper.selectById(id);
154 209 if (receivable == null) {
... ... @@ -187,4 +242,9 @@ public class CrmReceivableServiceImpl implements CrmReceivableService {
187 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 3 import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
4 4  
5 5 import java.util.Collection;
  6 +import java.util.List;
6 7  
7 8 /**
8 9 * 字典数据 API 接口
... ... @@ -39,4 +40,12 @@ public interface DictDataApi {
39 40 */
40 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 1 package cn.iocoder.yudao.module.system.api.dict;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
4 5 import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
5 6 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
6 7 import cn.iocoder.yudao.module.system.service.dict.DictDataService;
  8 +import jakarta.annotation.Resource;
7 9 import org.springframework.stereotype.Service;
8 10  
9   -import jakarta.annotation.Resource;
10 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 18 * 字典数据 API 实现类
... ... @@ -37,4 +42,13 @@ public class DictDataApiImpl implements DictDataApi {
37 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 99 */
100 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 11 import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO;
12 12 import cn.iocoder.yudao.module.system.dal.mysql.dict.DictDataMapper;
13 13 import com.google.common.annotations.VisibleForTesting;
  14 +import jakarta.annotation.Resource;
14 15 import lombok.extern.slf4j.Slf4j;
15 16 import org.springframework.stereotype.Service;
16 17  
17   -import jakarta.annotation.Resource;
18 18 import java.util.Collection;
19 19 import java.util.Comparator;
20 20 import java.util.List;
... ... @@ -169,4 +169,9 @@ public class DictDataServiceImpl implements DictDataService {
169 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 }
... ...