Commit e53a0ca8845229fb298c4d77da0f09315a6a5a4f
1 parent
a64da48a
✨ CRM:code review 客户导入
Showing
33 changed files
with
273 additions
and
213 deletions
yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java
| ... | ... | @@ -257,11 +257,11 @@ public class CollectionUtils { |
| 257 | 257 | return !CollectionUtil.isEmpty(from) ? from.get(0) : null; |
| 258 | 258 | } |
| 259 | 259 | |
| 260 | - public static <T> T findFirst(List<T> from, Predicate<T> predicate) { | |
| 260 | + public static <T> T findFirst(Collection<T> from, Predicate<T> predicate) { | |
| 261 | 261 | return findFirst(from, predicate, Function.identity()); |
| 262 | 262 | } |
| 263 | 263 | |
| 264 | - public static <T, U> U findFirst(List<T> from, Predicate<T> predicate, Function<T, U> func) { | |
| 264 | + public static <T, U> U findFirst(Collection<T> from, Predicate<T> predicate, Function<T, U> func) { | |
| 265 | 265 | if (CollUtil.isEmpty(from)) { |
| 266 | 266 | return null; |
| 267 | 267 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java
| ... | ... | @@ -4,7 +4,6 @@ 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; | |
| 8 | 7 | import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; |
| 9 | 8 | import cn.iocoder.yudao.framework.ip.core.Area; |
| 10 | 9 | import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; |
| ... | ... | @@ -72,26 +71,25 @@ public class AreaUtils { |
| 72 | 71 | * @param id 区域编号 |
| 73 | 72 | * @return 区域 |
| 74 | 73 | */ |
| 75 | - public static Area getArea(Integer id) { | |
| 74 | + public static Area parseArea(Integer id) { | |
| 76 | 75 | return areas.get(id); |
| 77 | 76 | } |
| 78 | 77 | |
| 79 | 78 | /** |
| 80 | 79 | * 获得指定区域对应的编号 |
| 81 | 80 | * |
| 82 | - * @param path 区域编号 | |
| 83 | - * @return 编号 | |
| 81 | + * @param pathStr 区域路径,例如说:河南省/石家庄市/新华区 | |
| 82 | + * @return 区域 | |
| 84 | 83 | */ |
| 85 | - public static Area getArea(String path) { | |
| 86 | - String[] paths = path.split("/"); | |
| 84 | + public static Area parseArea(String pathStr) { | |
| 85 | + String[] paths = pathStr.split("/"); | |
| 87 | 86 | Area area = null; |
| 88 | - for (int i = 0; i < paths.length; i++) { | |
| 89 | - final int finalI = i; | |
| 87 | + for (String path : paths) { | |
| 90 | 88 | if (area == null) { |
| 91 | - area = findFirst(convertList(areas.values(), a -> a), item -> ObjUtil.equal(paths[finalI], item.getName())); | |
| 92 | - continue; | |
| 89 | + area = findFirst(areas.values(), item -> item.getName().equals(path)); | |
| 90 | + } else { | |
| 91 | + area = findFirst(area.getChildren(), item -> item.getName().equals(path)); | |
| 93 | 92 | } |
| 94 | - area = findFirst(area.getChildren(), item -> ObjUtil.equal(paths[finalI], item.getName())); | |
| 95 | 93 | } |
| 96 | 94 | return area; |
| 97 | 95 | } |
| ... | ... | @@ -102,9 +100,9 @@ public class AreaUtils { |
| 102 | 100 | * @param areas 地区树 |
| 103 | 101 | * @return 所有节点的全路径名称 |
| 104 | 102 | */ |
| 105 | - public static List<String> getAllAreaNodePaths(List<Area> areas) { | |
| 103 | + public static List<String> getAreaNodePathList(List<Area> areas) { | |
| 106 | 104 | List<String> paths = new ArrayList<>(); |
| 107 | - areas.forEach(area -> traverse(area, "", paths)); | |
| 105 | + areas.forEach(area -> getAreaNodePathList(area, "", paths)); | |
| 108 | 106 | return paths; |
| 109 | 107 | } |
| 110 | 108 | |
| ... | ... | @@ -113,9 +111,9 @@ public class AreaUtils { |
| 113 | 111 | * |
| 114 | 112 | * @param node 父节点 |
| 115 | 113 | * @param path 全路径名称 |
| 116 | - * @param paths 全路径名称列表 | |
| 114 | + * @param paths 全路径名称列表,省份/城市/地区 | |
| 117 | 115 | */ |
| 118 | - private static void traverse(Area node, String path, List<String> paths) { | |
| 116 | + private static void getAreaNodePathList(Area node, String path, List<String> paths) { | |
| 119 | 117 | if (node == null) { |
| 120 | 118 | return; |
| 121 | 119 | } |
| ... | ... | @@ -124,7 +122,7 @@ public class AreaUtils { |
| 124 | 122 | paths.add(currentPath); |
| 125 | 123 | // 递归遍历子节点 |
| 126 | 124 | for (Area child : node.getChildren()) { |
| 127 | - traverse(child, currentPath, paths); | |
| 125 | + getAreaNodePathList(child, currentPath, paths); | |
| 128 | 126 | } |
| 129 | 127 | } |
| 130 | 128 | |
| ... | ... | @@ -195,7 +193,7 @@ public class AreaUtils { |
| 195 | 193 | */ |
| 196 | 194 | public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) { |
| 197 | 195 | for (int i = 0; i < Byte.MAX_VALUE; i++) { |
| 198 | - Area area = AreaUtils.getArea(id); | |
| 196 | + Area area = AreaUtils.parseArea(id); | |
| 199 | 197 | if (area == null) { |
| 200 | 198 | return null; |
| 201 | 199 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/IPUtils.java
| ... | ... | @@ -72,7 +72,7 @@ public class IPUtils { |
| 72 | 72 | * @return 地区 |
| 73 | 73 | */ |
| 74 | 74 | public static Area getArea(String ip) { |
| 75 | - return AreaUtils.getArea(getAreaId(ip)); | |
| 75 | + return AreaUtils.parseArea(getAreaId(ip)); | |
| 76 | 76 | } |
| 77 | 77 | |
| 78 | 78 | /** |
| ... | ... | @@ -82,6 +82,6 @@ public class IPUtils { |
| 82 | 82 | * @return 地区 |
| 83 | 83 | */ |
| 84 | 84 | public static Area getArea(long ip) { |
| 85 | - return AreaUtils.getArea(getAreaId(ip)); | |
| 85 | + return AreaUtils.parseArea(getAreaId(ip)); | |
| 86 | 86 | } |
| 87 | 87 | } | ... | ... |
yudao-framework/yudao-spring-boot-starter-biz-ip/src/test/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtilsTest.java
| ... | ... | @@ -17,7 +17,7 @@ public class AreaUtilsTest { |
| 17 | 17 | @Test |
| 18 | 18 | public void testGetArea() { |
| 19 | 19 | // 调用:北京 |
| 20 | - Area area = AreaUtils.getArea(110100); | |
| 20 | + Area area = AreaUtils.parseArea(110100); | |
| 21 | 21 | // 断言 |
| 22 | 22 | assertEquals(area.getId(), 110100); |
| 23 | 23 | assertEquals(area.getName(), "北京市"); | ... | ... |
yudao-framework/yudao-spring-boot-starter-excel/pom.xml
| ... | ... | @@ -49,7 +49,7 @@ |
| 49 | 49 | <dependency> |
| 50 | 50 | <groupId>cn.iocoder.boot</groupId> |
| 51 | 51 | <artifactId>yudao-spring-boot-starter-biz-ip</artifactId> |
| 52 | - <scope>provided</scope> <!-- 设置为 provided,只有 ExcelUtils 使用 --> | |
| 52 | + <optional>true</optional> <!-- 设置为 optional,只有在 AreaConvert 的时候使用 --> | |
| 53 | 53 | </dependency> |
| 54 | 54 | </dependencies> |
| 55 | 55 | ... | ... |
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java
| ... | ... | @@ -33,7 +33,7 @@ public class AreaConvert implements Converter<Object> { |
| 33 | 33 | GlobalConfiguration globalConfiguration) { |
| 34 | 34 | // 解析地区编号 |
| 35 | 35 | String label = readCellData.getStringValue(); |
| 36 | - Area area = AreaUtils.getArea(label); | |
| 36 | + Area area = AreaUtils.parseArea(label); | |
| 37 | 37 | if (area == null) { |
| 38 | 38 | log.error("[convertToJavaData][label({}) 解析不掉]", label); |
| 39 | 39 | return null; | ... | ... |
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java
| ... | ... | @@ -19,33 +19,40 @@ import java.util.List; |
| 19 | 19 | */ |
| 20 | 20 | public class SelectSheetWriteHandler implements SheetWriteHandler { |
| 21 | 21 | |
| 22 | + private static final String DICT_SHEET_NAME = "字典sheet"; | |
| 23 | + | |
| 24 | + // TODO @puhui999:key 不使用 int 值么?感觉不是很优雅哈。 | |
| 22 | 25 | private final List<KeyValue<Integer, List<String>>> selectMap; |
| 23 | 26 | |
| 24 | 27 | private static final char[] ALPHABET = new char[]{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', |
| 25 | 28 | 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; |
| 26 | 29 | |
| 27 | 30 | public SelectSheetWriteHandler(List<KeyValue<Integer, List<String>>> selectMap) { |
| 31 | + if (CollUtil.isEmpty(selectMap)) { | |
| 32 | + this.selectMap = null; | |
| 33 | + return; | |
| 34 | + } | |
| 28 | 35 | selectMap.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错 |
| 29 | 36 | this.selectMap = selectMap; |
| 30 | 37 | } |
| 31 | 38 | |
| 32 | 39 | @Override |
| 33 | 40 | public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { |
| 34 | - if (selectMap == null || CollUtil.isEmpty(selectMap)) { | |
| 41 | + if (CollUtil.isEmpty(selectMap)) { | |
| 35 | 42 | return; |
| 36 | 43 | } |
| 37 | - // 需要设置下拉框的sheet页 | |
| 38 | - Sheet curSheet = writeSheetHolder.getSheet(); | |
| 39 | - DataValidationHelper helper = curSheet.getDataValidationHelper(); | |
| 40 | - String dictSheetName = "字典sheet"; | |
| 44 | + // 需要设置下拉框的 sheet 页 | |
| 45 | + Sheet currentSheet = writeSheetHolder.getSheet(); | |
| 46 | + DataValidationHelper helper = currentSheet.getDataValidationHelper(); | |
| 41 | 47 | Workbook workbook = writeWorkbookHolder.getWorkbook(); |
| 42 | - // 数据字典的sheet页 | |
| 43 | - Sheet dictSheet = workbook.createSheet(dictSheetName); | |
| 48 | + | |
| 49 | + // 数据字典的 sheet 页 | |
| 50 | + Sheet dictSheet = workbook.createSheet(DICT_SHEET_NAME); | |
| 44 | 51 | for (KeyValue<Integer, List<String>> keyValue : selectMap) { |
| 45 | 52 | // 设置下拉单元格的首行、末行、首列、末列 |
| 46 | 53 | CellRangeAddressList rangeAddressList = new CellRangeAddressList(1, 65533, keyValue.getKey(), keyValue.getKey()); |
| 47 | 54 | int rowLen = keyValue.getValue().size(); |
| 48 | - // 设置字典sheet页的值 每一列一个字典项 | |
| 55 | + // 设置字典 sheet 页的值 每一列一个字典项 | |
| 49 | 56 | for (int i = 0; i < rowLen; i++) { |
| 50 | 57 | Row row = dictSheet.getRow(i); |
| 51 | 58 | if (row == null) { |
| ... | ... | @@ -53,18 +60,18 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { |
| 53 | 60 | } |
| 54 | 61 | row.createCell(keyValue.getKey()).setCellValue(keyValue.getValue().get(i)); |
| 55 | 62 | } |
| 56 | - String excelColumn = getExcelColumn(keyValue.getKey()); | |
| 57 | - // 下拉框数据来源 eg:字典sheet!$B1:$B2 | |
| 58 | - String refers = dictSheetName + "!$" + excelColumn + "$1:$" + excelColumn + "$" + rowLen; | |
| 59 | - // 创建可被其他单元格引用的名称 | |
| 63 | + | |
| 64 | + // TODO @puhui999:下面 1. 2.1 2.2 2.3 我是按照已经理解的,调整了下格式;这样可读性更好;在 52 到 62 行,你可以看看,是不是也弄下序号; | |
| 65 | + // 1. 创建可被其他单元格引用的名称 | |
| 60 | 66 | 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 | - // 设置约束 | |
| 67 | + // TODO @puhui999:下面的 excelColumn 和 refers 两行,是不是可以封装成一个方法,替代 getExcelColumn; | |
| 68 | + String excelColumn = getExcelColumn(keyValue.getKey()); | |
| 69 | + String refers = DICT_SHEET_NAME + "!$" + excelColumn + "$1:$" + excelColumn + "$" + rowLen; // 下拉框数据来源 eg:字典sheet!$B1:$B2 | |
| 70 | + name.setNameName("dict" + keyValue.getKey()); // 设置名称的名字 | |
| 71 | + name.setRefersToFormula(refers); // 设置公式 | |
| 72 | + | |
| 73 | + // 2.1 设置约束 | |
| 74 | + DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + keyValue.getKey()); // 设置引用约束 | |
| 68 | 75 | DataValidation validation = helper.createValidation(constraint, rangeAddressList); |
| 69 | 76 | if (validation instanceof HSSFDataValidation) { |
| 70 | 77 | validation.setSuppressDropDownArrow(false); |
| ... | ... | @@ -72,10 +79,10 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { |
| 72 | 79 | validation.setSuppressDropDownArrow(true); |
| 73 | 80 | validation.setShowErrorBox(true); |
| 74 | 81 | } |
| 75 | - // 阻止输入非下拉框的值 | |
| 82 | + // 2.2 阻止输入非下拉框的值 | |
| 76 | 83 | validation.setErrorStyle(DataValidation.ErrorStyle.STOP); |
| 77 | 84 | validation.createErrorBox("提示", "此值不存在于下拉选择中!"); |
| 78 | - // 添加下拉框约束 | |
| 85 | + // 2.3 添加下拉框约束 | |
| 79 | 86 | writeSheetHolder.getSheet().addValidationData(validation); |
| 80 | 87 | } |
| 81 | 88 | } |
| ... | ... | @@ -86,8 +93,9 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { |
| 86 | 93 | * @param num 数字 |
| 87 | 94 | * @return 字母 |
| 88 | 95 | */ |
| 96 | + // TODO @puhui999:这个是必须字母列哇?还是数字其实也可以哈?主要想看看,怎么能把这个逻辑,进一步简化 | |
| 89 | 97 | private String getExcelColumn(int num) { |
| 90 | - String column = ""; | |
| 98 | + String column; | |
| 91 | 99 | int len = ALPHABET.length - 1; |
| 92 | 100 | int first = num / len; |
| 93 | 101 | int second = num % len; |
| ... | ... | @@ -96,9 +104,9 @@ public class SelectSheetWriteHandler implements SheetWriteHandler { |
| 96 | 104 | } else { |
| 97 | 105 | column = ALPHABET[first - 1] + ""; |
| 98 | 106 | if (second == 0) { |
| 99 | - column = column + ALPHABET[len] + ""; | |
| 107 | + column = column + ALPHABET[len]; | |
| 100 | 108 | } else { |
| 101 | - column = column + ALPHABET[second - 1] + ""; | |
| 109 | + column = column + ALPHABET[second - 1]; | |
| 102 | 110 | } |
| 103 | 111 | } |
| 104 | 112 | return column; | ... | ... |
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
| ... | ... | @@ -33,15 +33,7 @@ public class ExcelUtils { |
| 33 | 33 | */ |
| 34 | 34 | public static <T> void write(HttpServletResponse response, String filename, String sheetName, |
| 35 | 35 | Class<T> head, List<T> data) throws IOException { |
| 36 | - // 输出 Excel | |
| 37 | - EasyExcel.write(response.getOutputStream(), head) | |
| 38 | - .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 | |
| 39 | - .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度 | |
| 40 | - .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度 | |
| 41 | - .sheet(sheetName).doWrite(data); | |
| 42 | - // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了 | |
| 43 | - response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name())); | |
| 44 | - response.setContentType("application/vnd.ms-excel;charset=UTF-8"); | |
| 36 | + write(response, filename, sheetName, head, data, null); | |
| 45 | 37 | } |
| 46 | 38 | |
| 47 | 39 | /** | ... | ... |
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
| ... | ... | @@ -14,6 +14,7 @@ public interface ErrorCodeConstants { |
| 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 | 16 | ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态"); |
| 17 | + ErrorCode CONTRACT_NO_EXISTS = new ErrorCode(1_020_000_004, "生成合同序列号重复,请重试"); | |
| 17 | 18 | |
| 18 | 19 | // ========== 线索管理 1-020-001-000 ========== |
| 19 | 20 | ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在"); | ... | ... |
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/receivable/CrmReturnTypeEnum.java renamed to yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/receivable/CrmReceivableReturnTypeEnum.java
| ... | ... | @@ -13,26 +13,18 @@ import java.util.Arrays; |
| 13 | 13 | */ |
| 14 | 14 | @Getter |
| 15 | 15 | @AllArgsConstructor |
| 16 | -public enum CrmReturnTypeEnum implements IntArrayValuable { | |
| 16 | +public enum CrmReceivableReturnTypeEnum implements IntArrayValuable { | |
| 17 | 17 | |
| 18 | - // 支票 | |
| 19 | 18 | CHECK(1, "支票"), |
| 20 | - // 现金 | |
| 21 | 19 | CASH(2, "现金"), |
| 22 | - // 邮政汇款 | |
| 23 | 20 | POSTAL_REMITTANCE(3, "邮政汇款"), |
| 24 | - // 电汇 | |
| 25 | 21 | TELEGRAPHIC_TRANSFER(4, "电汇"), |
| 26 | - // 网上转账 | |
| 27 | 22 | ONLINE_TRANSFER(5, "网上转账"), |
| 28 | - // 支付宝 | |
| 29 | 23 | ALIPAY(6, "支付宝"), |
| 30 | - // 微信支付 | |
| 31 | 24 | WECHAT_PAY(7, "微信支付"), |
| 32 | - // 其他 | |
| 33 | 25 | OTHER(8, "其它"); |
| 34 | 26 | |
| 35 | - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmReturnTypeEnum::getType).toArray(); | |
| 27 | + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmReceivableReturnTypeEnum::getType).toArray(); | |
| 36 | 28 | |
| 37 | 29 | /** |
| 38 | 30 | * 类型 | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
| ... | ... | @@ -268,6 +268,24 @@ public class CrmCustomerController { |
| 268 | 268 | ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list, builderSelectMap()); |
| 269 | 269 | } |
| 270 | 270 | |
| 271 | + private List<KeyValue<Integer, List<String>>> builderSelectMap() { | |
| 272 | + List<KeyValue<Integer, List<String>>> selectMap = new ArrayList<>(); | |
| 273 | + // 获取地区下拉数据 | |
| 274 | + // TODO @puhui999:嘿嘿,这里改成省份、城市、区域,三个选项,难度大么? | |
| 275 | + Area area = AreaUtils.parseArea(Area.ID_CHINA); | |
| 276 | + selectMap.add(new KeyValue<>(6, AreaUtils.getAreaNodePathList(area.getChildren()))); | |
| 277 | + // 获取客户所属行业 | |
| 278 | + List<String> customerIndustries = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_INDUSTRY); | |
| 279 | + selectMap.add(new KeyValue<>(8, customerIndustries)); | |
| 280 | + // 获取客户等级 | |
| 281 | + List<String> customerLevels = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_LEVEL); | |
| 282 | + selectMap.add(new KeyValue<>(9, customerLevels)); | |
| 283 | + // 获取客户来源 | |
| 284 | + List<String> customerSources = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_SOURCE); | |
| 285 | + selectMap.add(new KeyValue<>(10, customerSources)); | |
| 286 | + return selectMap; | |
| 287 | + } | |
| 288 | + | |
| 271 | 289 | @PostMapping("/import") |
| 272 | 290 | @Operation(summary = "导入客户") |
| 273 | 291 | @PreAuthorize("@ss.hasPermission('system:customer:import')") |
| ... | ... | @@ -321,21 +339,4 @@ public class CrmCustomerController { |
| 321 | 339 | return success(true); |
| 322 | 340 | } |
| 323 | 341 | |
| 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 | - | |
| 341 | 342 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java
| ... | ... | @@ -107,7 +107,6 @@ public class CrmReceivableController { |
| 107 | 107 | return success(buildReceivableDetailPage(pageResult)); |
| 108 | 108 | } |
| 109 | 109 | |
| 110 | - // TODO 芋艿:后面在优化导出 | |
| 111 | 110 | @GetMapping("/export-excel") |
| 112 | 111 | @Operation(summary = "导出回款 Excel") |
| 113 | 112 | @PreAuthorize("@ss.hasPermission('crm:receivable:export')") | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java
| ... | ... | @@ -28,7 +28,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; |
| 28 | 28 | import jakarta.annotation.Resource; |
| 29 | 29 | import jakarta.servlet.http.HttpServletResponse; |
| 30 | 30 | import jakarta.validation.Valid; |
| 31 | -import org.springframework.context.annotation.Lazy; | |
| 32 | 31 | import org.springframework.security.access.prepost.PreAuthorize; |
| 33 | 32 | import org.springframework.validation.annotation.Validated; |
| 34 | 33 | import org.springframework.web.bind.annotation.*; |
| ... | ... | @@ -56,7 +55,6 @@ public class CrmReceivablePlanController { |
| 56 | 55 | @Resource |
| 57 | 56 | private CrmReceivableService receivableService; |
| 58 | 57 | @Resource |
| 59 | - @Lazy | |
| 60 | 58 | private CrmContractService contractService; |
| 61 | 59 | @Resource |
| 62 | 60 | private CrmCustomerService customerService; | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java
| ... | ... | @@ -6,6 +6,7 @@ import lombok.Data; |
| 6 | 6 | import java.math.BigDecimal; |
| 7 | 7 | import java.time.LocalDateTime; |
| 8 | 8 | |
| 9 | +// TODO @puhui999:缺导出 | |
| 9 | 10 | @Schema(description = "管理后台 - CRM 回款计划 Response VO") |
| 10 | 11 | @Data |
| 11 | 12 | public class CrmReceivablePlanRespVO { |
| ... | ... | @@ -13,35 +14,44 @@ public class CrmReceivablePlanRespVO { |
| 13 | 14 | @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 14 | 15 | private Long id; |
| 15 | 16 | |
| 16 | - @Schema(description = "回款编号", example = "19852") | |
| 17 | - private Long receivableId; | |
| 18 | - | |
| 19 | 17 | @Schema(description = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 20 | 18 | private Integer period; |
| 21 | 19 | |
| 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 | 20 | @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 35 | 21 | private Long customerId; |
| 22 | + @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test") | |
| 23 | + private String customerName; | |
| 36 | 24 | |
| 37 | 25 | @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 38 | 26 | private Long contractId; |
| 27 | + @Schema(description = "合同编号", example = "Q110") | |
| 28 | + private String contractNo; | |
| 39 | 29 | |
| 40 | 30 | @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 41 | 31 | private Long ownerUserId; |
| 32 | + @Schema(description = "负责人", example = "test") | |
| 33 | + private String ownerUserName; | |
| 34 | + | |
| 35 | + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") | |
| 36 | + private LocalDateTime returnTime; | |
| 37 | + | |
| 38 | + @Schema(description = "回款方式", example = "1") | |
| 39 | + private Integer returnType; // 来自 Receivable 的 returnType 字段 | |
| 42 | 40 | |
| 43 | - @Schema(description = "显示顺序") | |
| 44 | - private Integer sort; | |
| 41 | + @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") | |
| 42 | + private BigDecimal price; | |
| 43 | + | |
| 44 | + @Schema(description = "回款编号", example = "19852") | |
| 45 | + private Long receivableId; | |
| 46 | + | |
| 47 | + @Schema(description = "完成状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") | |
| 48 | + private Boolean finishStatus; | |
| 49 | + | |
| 50 | + @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") | |
| 51 | + private Integer remindDays; | |
| 52 | + | |
| 53 | + @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") | |
| 54 | + private LocalDateTime remindTime; | |
| 45 | 55 | |
| 46 | 56 | @Schema(description = "备注", example = "备注") |
| 47 | 57 | private String remark; |
| ... | ... | @@ -49,25 +59,10 @@ public class CrmReceivablePlanRespVO { |
| 49 | 59 | @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) |
| 50 | 60 | private LocalDateTime createTime; |
| 51 | 61 | |
| 52 | - @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test") | |
| 53 | - private String customerName; | |
| 54 | - | |
| 55 | - @Schema(description = "合同编号", example = "Q110") | |
| 56 | - private String contractNo; | |
| 57 | - | |
| 58 | - @Schema(description = "负责人", example = "test") | |
| 59 | - private String ownerUserName; | |
| 60 | - | |
| 61 | 62 | @Schema(description = "创建人", example = "25682") |
| 62 | 63 | private String creator; |
| 63 | 64 | |
| 64 | 65 | @Schema(description = "创建人名字", example = "test") |
| 65 | 66 | private String creatorName; |
| 66 | 67 | |
| 67 | - @Schema(description = "完成状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") | |
| 68 | - private Boolean finishStatus; | |
| 69 | - | |
| 70 | - @Schema(description = "回款方式", example = "1") | |
| 71 | - private Integer returnType; // 来自 Receivable 的 returnType 字段 | |
| 72 | - | |
| 73 | 68 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanSaveReqVO.java
| ... | ... | @@ -14,26 +14,6 @@ public class CrmReceivablePlanSaveReqVO { |
| 14 | 14 | @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 15 | 15 | private Long id; |
| 16 | 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 | 17 | @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") |
| 38 | 18 | @NotNull(message = "客户编号不能为空") |
| 39 | 19 | private Long customerId; |
| ... | ... | @@ -46,8 +26,19 @@ public class CrmReceivablePlanSaveReqVO { |
| 46 | 26 | @NotNull(message = "负责人编号不能为空") |
| 47 | 27 | private Long ownerUserId; |
| 48 | 28 | |
| 49 | - @Schema(description = "显示顺序") | |
| 50 | - private Integer sort; | |
| 29 | + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") | |
| 30 | + @NotNull(message = "计划回款日期不能为空") | |
| 31 | + private LocalDateTime returnTime; | |
| 32 | + | |
| 33 | + @Schema(description = "回款方式", example = "1") | |
| 34 | + private Integer returnType; | |
| 35 | + | |
| 36 | + @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") | |
| 37 | + @NotNull(message = "计划回款金额不能为空") | |
| 38 | + private BigDecimal price; | |
| 39 | + | |
| 40 | + @Schema(description = "提前几天提醒", example = "1") | |
| 41 | + private Integer remindDays; | |
| 51 | 42 | |
| 52 | 43 | @Schema(description = "备注", example = "备注") |
| 53 | 44 | private String remark; | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableSaveReqVO.java
| 1 | 1 | package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.validation.InEnum; |
| 4 | -import cn.iocoder.yudao.module.crm.enums.receivable.CrmReturnTypeEnum; | |
| 4 | +import cn.iocoder.yudao.module.crm.enums.receivable.CrmReceivableReturnTypeEnum; | |
| 5 | 5 | import io.swagger.v3.oas.annotations.media.Schema; |
| 6 | 6 | import jakarta.validation.constraints.NotNull; |
| 7 | 7 | import lombok.Data; |
| ... | ... | @@ -23,7 +23,7 @@ public class CrmReceivableSaveReqVO { |
| 23 | 23 | private Long planId; // 不是通过回款计划创建的回款没有回款计划编号 |
| 24 | 24 | |
| 25 | 25 | @Schema(description = "回款方式", example = "2") |
| 26 | - @InEnum(CrmReturnTypeEnum.class) | |
| 26 | + @InEnum(CrmReceivableReturnTypeEnum.class) | |
| 27 | 27 | private Integer returnType; |
| 28 | 28 | |
| 29 | 29 | @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java
| ... | ... | @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | 4 | import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; |
| 5 | 5 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; |
| 6 | 6 | import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; |
| 7 | -import cn.iocoder.yudao.module.crm.enums.receivable.CrmReturnTypeEnum; | |
| 7 | +import cn.iocoder.yudao.module.crm.enums.receivable.CrmReceivableReturnTypeEnum; | |
| 8 | 8 | import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; |
| 9 | 9 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 10 | 10 | import com.baomidou.mybatisplus.annotation.TableId; |
| ... | ... | @@ -60,7 +60,7 @@ public class CrmReceivableDO extends BaseDO { |
| 60 | 60 | */ |
| 61 | 61 | private LocalDateTime returnTime; |
| 62 | 62 | /** |
| 63 | - * 回款方式,关联枚举{@link CrmReturnTypeEnum} | |
| 63 | + * 回款方式,关联枚举{@link CrmReceivableReturnTypeEnum} | |
| 64 | 64 | */ |
| 65 | 65 | private Integer returnType; |
| 66 | 66 | /** | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java
| ... | ... | @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.receivable; |
| 3 | 3 | import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; |
| 4 | 4 | import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; |
| 5 | 5 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; |
| 6 | -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 6 | +import cn.iocoder.yudao.module.crm.enums.receivable.CrmReceivableReturnTypeEnum; | |
| 7 | 7 | import com.baomidou.mybatisplus.annotation.KeySequence; |
| 8 | 8 | import com.baomidou.mybatisplus.annotation.TableId; |
| 9 | 9 | import com.baomidou.mybatisplus.annotation.TableName; |
| ... | ... | @@ -13,7 +13,7 @@ import java.math.BigDecimal; |
| 13 | 13 | import java.time.LocalDateTime; |
| 14 | 14 | |
| 15 | 15 | /** |
| 16 | - * 回款计划 DO | |
| 16 | + * CRM 回款计划 DO | |
| 17 | 17 | * |
| 18 | 18 | * @author 芋道源码 |
| 19 | 19 | */ |
| ... | ... | @@ -37,49 +37,60 @@ public class CrmReceivablePlanDO extends BaseDO { |
| 37 | 37 | */ |
| 38 | 38 | private Integer period; |
| 39 | 39 | /** |
| 40 | - * 回款编号,关联 {@link CrmReceivableDO#getId()} | |
| 40 | + * 客户编号 | |
| 41 | + * | |
| 42 | + * 关联 {@link CrmCustomerDO#getId()} | |
| 41 | 43 | */ |
| 42 | - private Long receivableId; | |
| 44 | + private Long customerId; | |
| 43 | 45 | /** |
| 44 | - * 计划回款金额,单位:元 | |
| 46 | + * 合同编号 | |
| 47 | + * | |
| 48 | + * 关联 {@link CrmContractDO#getId()} | |
| 45 | 49 | */ |
| 46 | - private BigDecimal price; | |
| 50 | + private Long contractId; | |
| 51 | + | |
| 52 | + /** | |
| 53 | + * 负责人编号 | |
| 54 | + * | |
| 55 | + * 关联 AdminUserDO 的 id 字段 | |
| 56 | + */ | |
| 57 | + private Long ownerUserId; | |
| 58 | + | |
| 47 | 59 | /** |
| 48 | 60 | * 计划回款日期 |
| 49 | 61 | */ |
| 50 | 62 | private LocalDateTime returnTime; |
| 51 | 63 | /** |
| 52 | - * 提前几天提醒 | |
| 64 | + * 回款类型 | |
| 65 | + * | |
| 66 | + * 枚举 {@link CrmReceivableReturnTypeEnum} | |
| 53 | 67 | */ |
| 54 | - private Integer remindDays; | |
| 68 | + private Integer returnType; | |
| 55 | 69 | /** |
| 56 | - * 提醒日期 | |
| 70 | + * 计划回款金额,单位:元 | |
| 57 | 71 | */ |
| 58 | - private LocalDateTime remindTime; | |
| 72 | + private BigDecimal price; | |
| 73 | + | |
| 59 | 74 | /** |
| 60 | - * 客户编号,关联 {@link CrmCustomerDO#getId()} | |
| 75 | + * 回款编号,关联 {@link CrmReceivableDO#getId()} | |
| 61 | 76 | */ |
| 62 | - private Long customerId; | |
| 77 | + private Long receivableId; | |
| 63 | 78 | /** |
| 64 | - * 合同编号,关联 {@link CrmContractDO#getId()} | |
| 79 | + * 完成状态 | |
| 65 | 80 | */ |
| 66 | - private Long contractId; | |
| 81 | + private Boolean finishStatus; | |
| 82 | + | |
| 67 | 83 | /** |
| 68 | - * 负责人编号,关联 {@link AdminUserRespDTO#getId()} | |
| 84 | + * 提前几天提醒 | |
| 69 | 85 | */ |
| 70 | - private Long ownerUserId; | |
| 86 | + private Integer remindDays; | |
| 71 | 87 | /** |
| 72 | - * 显示顺序 | |
| 88 | + * 提醒日期 | |
| 73 | 89 | */ |
| 74 | - private Integer sort; | |
| 90 | + private LocalDateTime remindTime; | |
| 75 | 91 | /** |
| 76 | 92 | * 备注 |
| 77 | 93 | */ |
| 78 | 94 | private String remark; |
| 79 | 95 | |
| 80 | - /** | |
| 81 | - * 完成状态 | |
| 82 | - */ | |
| 83 | - private Boolean finishStatus; | |
| 84 | - | |
| 85 | 96 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
| ... | ... | @@ -11,7 +11,6 @@ import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; |
| 11 | 11 | import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; |
| 12 | 12 | import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; |
| 13 | 13 | import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; |
| 14 | -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |
| 15 | 14 | import org.apache.ibatis.annotations.Mapper; |
| 16 | 15 | |
| 17 | 16 | import java.time.LocalDateTime; |
| ... | ... | @@ -26,10 +25,8 @@ import java.util.List; |
| 26 | 25 | @Mapper |
| 27 | 26 | public interface CrmContractMapper extends BaseMapperX<CrmContractDO> { |
| 28 | 27 | |
| 29 | - default int updateOwnerUserIdById(Long id, Long ownerUserId) { | |
| 30 | - return update(new LambdaUpdateWrapper<CrmContractDO>() | |
| 31 | - .eq(CrmContractDO::getId, id) | |
| 32 | - .set(CrmContractDO::getOwnerUserId, ownerUserId)); | |
| 28 | + default CrmContractDO selectByNo(String no) { | |
| 29 | + return selectOne(CrmContractDO::getNo, no); | |
| 33 | 30 | } |
| 34 | 31 | |
| 35 | 32 | default PageResult<CrmContractDO> selectPageByCustomerId(CrmContractPageReqVO pageReqVO) { | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/RedisKeyConstants.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.crm.dal.redis; | |
| 2 | + | |
| 3 | +/** | |
| 4 | + * CRM Redis Key 枚举类 | |
| 5 | + * | |
| 6 | + * @author 芋道源码 | |
| 7 | + */ | |
| 8 | +public interface RedisKeyConstants { | |
| 9 | + | |
| 10 | + /** | |
| 11 | + * 序号的缓存 | |
| 12 | + * | |
| 13 | + * KEY 格式:trade_no:{prefix} | |
| 14 | + * VALUE 数据格式:编号自增 | |
| 15 | + */ | |
| 16 | + String NO = "crm:seq_no:"; | |
| 17 | + | |
| 18 | +} | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/no/CrmNoRedisDAO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.crm.dal.redis.no; | |
| 2 | + | |
| 3 | +import cn.hutool.core.date.DatePattern; | |
| 4 | +import cn.hutool.core.date.DateUtil; | |
| 5 | +import cn.iocoder.yudao.module.crm.dal.redis.RedisKeyConstants; | |
| 6 | +import jakarta.annotation.Resource; | |
| 7 | +import org.springframework.data.redis.core.StringRedisTemplate; | |
| 8 | +import org.springframework.stereotype.Repository; | |
| 9 | + | |
| 10 | +import java.time.Duration; | |
| 11 | +import java.time.LocalDateTime; | |
| 12 | + | |
| 13 | + | |
| 14 | +/** | |
| 15 | + * Crm 订单序号的 Redis DAO | |
| 16 | + * | |
| 17 | + * @author HUIHUI | |
| 18 | + */ | |
| 19 | +@Repository | |
| 20 | +public class CrmNoRedisDAO { | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 合同 {@link cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO} | |
| 24 | + */ | |
| 25 | + public static final String CONTRACT_NO_PREFIX = "HT"; | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * 还款 {@link cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO} | |
| 29 | + */ | |
| 30 | + public static final String RECEIVABLE_PREFIX = "HK"; | |
| 31 | + | |
| 32 | + @Resource | |
| 33 | + private StringRedisTemplate stringRedisTemplate; | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * 生成序号,使用当前日期,格式为 {PREFIX} + yyyyMMdd + 6 位自增 | |
| 37 | + * 例如说:QTRK 202109 000001 (没有中间空格) | |
| 38 | + * | |
| 39 | + * @param prefix 前缀 | |
| 40 | + * @return 序号 | |
| 41 | + */ | |
| 42 | + public String generate(String prefix) { | |
| 43 | + // 递增序号 | |
| 44 | + String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN); | |
| 45 | + String key = RedisKeyConstants.NO + noPrefix; | |
| 46 | + Long no = stringRedisTemplate.opsForValue().increment(key); | |
| 47 | + // 设置过期时间 | |
| 48 | + stringRedisTemplate.expire(key, Duration.ofDays(1L)); | |
| 49 | + return noPrefix + String.format("%06d", no); | |
| 50 | + } | |
| 51 | + | |
| 52 | +} | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
| ... | ... | @@ -17,6 +17,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; |
| 17 | 17 | import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO; |
| 18 | 18 | import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper; |
| 19 | 19 | import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractProductMapper; |
| 20 | +import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO; | |
| 20 | 21 | import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; |
| 21 | 22 | import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; |
| 22 | 23 | import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; |
| ... | ... | @@ -70,6 +71,9 @@ public class CrmContractServiceImpl implements CrmContractService { |
| 70 | 71 | private CrmContractProductMapper contractProductMapper; |
| 71 | 72 | |
| 72 | 73 | @Resource |
| 74 | + private CrmNoRedisDAO noRedisDAO; | |
| 75 | + | |
| 76 | + @Resource | |
| 73 | 77 | private CrmPermissionService crmPermissionService; |
| 74 | 78 | @Resource |
| 75 | 79 | private CrmProductService productService; |
| ... | ... | @@ -94,11 +98,14 @@ public class CrmContractServiceImpl implements CrmContractService { |
| 94 | 98 | List<CrmContractProductDO> contractProducts = validateContractProducts(createReqVO.getProducts()); |
| 95 | 99 | // 1.2 校验关联字段 |
| 96 | 100 | validateRelationDataExists(createReqVO); |
| 97 | - // TODO 芋艿:生成 no | |
| 101 | + // 1.3 生成序号 | |
| 102 | + String no = noRedisDAO.generate(CrmNoRedisDAO.CONTRACT_NO_PREFIX); | |
| 103 | + if (contractMapper.selectByNo(no) != null) { | |
| 104 | + throw exception(CONTRACT_NO_EXISTS); | |
| 105 | + } | |
| 98 | 106 | |
| 99 | 107 | // 2.1 插入合同 |
| 100 | - CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class); | |
| 101 | - contract.setNo(System.currentTimeMillis() + ""); // TODO | |
| 108 | + CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setNo(no); | |
| 102 | 109 | calculateTotalPrice(contract, contractProducts); |
| 103 | 110 | contractMapper.insert(contract); |
| 104 | 111 | // 2.2 插入合同关联商品 |
| ... | ... | @@ -247,7 +254,7 @@ public class CrmContractServiceImpl implements CrmContractService { |
| 247 | 254 | crmPermissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CONTRACT.getType(), |
| 248 | 255 | reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel())); |
| 249 | 256 | // 2.2 设置负责人 |
| 250 | - contractMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); | |
| 257 | + contractMapper.updateById(new CrmContractDO().setId(reqVO.getId()).setOwnerUserId(reqVO.getNewOwnerUserId())); | |
| 251 | 258 | |
| 252 | 259 | // 3. 记录转移日志 |
| 253 | 260 | LogRecordContext.putVariable("contract", contract); | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java
| ... | ... | @@ -23,7 +23,7 @@ import com.mzt.logapi.context.LogRecordContext; |
| 23 | 23 | import com.mzt.logapi.service.impl.DiffParseFunction; |
| 24 | 24 | import com.mzt.logapi.starter.annotation.LogRecord; |
| 25 | 25 | import jakarta.annotation.Resource; |
| 26 | -import org.hibernate.validator.internal.util.stereotypes.Lazy; | |
| 26 | +import org.springframework.context.annotation.Lazy; | |
| 27 | 27 | import org.springframework.stereotype.Service; |
| 28 | 28 | import org.springframework.transaction.annotation.Transactional; |
| 29 | 29 | import org.springframework.validation.annotation.Validated; |
| ... | ... | @@ -57,6 +57,7 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { |
| 57 | 57 | private CrmCustomerService customerService; |
| 58 | 58 | @Resource |
| 59 | 59 | private CrmPermissionService permissionService; |
| 60 | + | |
| 60 | 61 | @Resource |
| 61 | 62 | private AdminUserApi adminUserApi; |
| 62 | 63 | |
| ... | ... | @@ -72,15 +73,16 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { |
| 72 | 73 | int period = (int) (count + 1); |
| 73 | 74 | createReqVO.setPeriod(createReqVO.getPeriod() != period ? period : createReqVO.getPeriod()); // 如果期数不对则纠正 |
| 74 | 75 | |
| 75 | - // 2.1 插入 | |
| 76 | + // 2. 插入还款计划 | |
| 76 | 77 | CrmReceivablePlanDO receivablePlan = BeanUtils.toBean(createReqVO, CrmReceivablePlanDO.class).setId(null).setFinishStatus(false); |
| 77 | 78 | receivablePlanMapper.insert(receivablePlan); |
| 78 | - // 2.2 创建数据权限 | |
| 79 | + | |
| 80 | + // 3. 创建数据权限 | |
| 79 | 81 | permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(createReqVO.getOwnerUserId()) |
| 80 | 82 | .setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()).setBizId(receivablePlan.getId()) |
| 81 | 83 | .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); |
| 82 | 84 | |
| 83 | - // 3. 记录操作日志上下文 | |
| 85 | + // 4. 记录操作日志上下文 | |
| 84 | 86 | LogRecordContext.putVariable("receivablePlan", receivablePlan); |
| 85 | 87 | return receivablePlan.getId(); |
| 86 | 88 | } | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java
| ... | ... | @@ -31,6 +31,7 @@ import com.mzt.logapi.service.impl.DiffParseFunction; |
| 31 | 31 | import com.mzt.logapi.starter.annotation.LogRecord; |
| 32 | 32 | import jakarta.annotation.Resource; |
| 33 | 33 | import lombok.extern.slf4j.Slf4j; |
| 34 | +import org.springframework.context.annotation.Lazy; | |
| 34 | 35 | import org.springframework.stereotype.Service; |
| 35 | 36 | import org.springframework.transaction.annotation.Transactional; |
| 36 | 37 | import org.springframework.validation.annotation.Validated; |
| ... | ... | @@ -66,10 +67,12 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { |
| 66 | 67 | private CrmContractService contractService; |
| 67 | 68 | @Resource |
| 68 | 69 | private CrmCustomerService customerService; |
| 69 | - @Resource | |
| 70 | +// @Resource | |
| 71 | + @Lazy // 延迟加载,避免循环依赖 | |
| 70 | 72 | private CrmReceivablePlanService receivablePlanService; |
| 71 | 73 | @Resource |
| 72 | 74 | private CrmPermissionService permissionService; |
| 75 | + | |
| 73 | 76 | @Resource |
| 74 | 77 | private AdminUserApi adminUserApi; |
| 75 | 78 | @Resource | ... | ... |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmAuditStatusUtils.java
| 1 | 1 | package cn.iocoder.yudao.module.crm.util; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.lang.Assert; |
| 4 | -import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; | |
| 5 | 4 | import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; |
| 6 | 5 | import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; |
| 7 | 6 | |
| ... | ... | @@ -18,23 +17,11 @@ public class CrmAuditStatusUtils { |
| 18 | 17 | * @param bpmResult BPM 审批结果 |
| 19 | 18 | */ |
| 20 | 19 | public static Integer convertBpmResultToAuditStatus(Integer bpmResult) { |
| 21 | - Assert.isTrue(isEndResult(bpmResult), "BPM 审批结果({}) 转换失败, 流程状态不是最终结果", bpmResult); | |
| 22 | 20 | Integer auditStatus = BpmProcessInstanceResultEnum.APPROVE.getResult().equals(bpmResult) ? CrmAuditStatusEnum.APPROVE.getStatus() |
| 23 | 21 | : BpmProcessInstanceResultEnum.REJECT.getResult().equals(bpmResult) ? CrmAuditStatusEnum.REJECT.getStatus() |
| 24 | - : BpmProcessInstanceResultEnum.CANCEL.getResult(); | |
| 22 | + : BpmProcessInstanceResultEnum.CANCEL.getResult().equals(bpmResult) ? BpmProcessInstanceResultEnum.CANCEL.getResult() : null; | |
| 25 | 23 | Assert.notNull(auditStatus, "BPM 审批结果({}) 转换失败", bpmResult); |
| 26 | 24 | return auditStatus; |
| 27 | 25 | } |
| 28 | 26 | |
| 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 | 27 | } | ... | ... |
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/RedisKeyConstants.java
yudao-module-erp/yudao-module-erp-biz/src/main/java/cn/iocoder/yudao/module/erp/dal/redis/no/ErpNoRedisDAO.java
yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java
| ... | ... | @@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; |
| 5 | 5 | import java.util.Collection; |
| 6 | 6 | import java.util.List; |
| 7 | 7 | |
| 8 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 9 | + | |
| 8 | 10 | /** |
| 9 | 11 | * 字典数据 API 接口 |
| 10 | 12 | * |
| ... | ... | @@ -41,11 +43,22 @@ public interface DictDataApi { |
| 41 | 43 | DictDataRespDTO parseDictData(String type, String label); |
| 42 | 44 | |
| 43 | 45 | /** |
| 46 | + * 获得指定字典类型的字典数据列表 | |
| 47 | + * | |
| 48 | + * @param dictType 字典类型 | |
| 49 | + * @return 字典数据列表 | |
| 50 | + */ | |
| 51 | + List<DictDataRespDTO> getDictDataList(String dictType); | |
| 52 | + | |
| 53 | + /** | |
| 44 | 54 | * 获得字典数据标签列表 |
| 45 | 55 | * |
| 46 | 56 | * @param dictType 字典类型 |
| 47 | 57 | * @return 字典数据标签列表 |
| 48 | 58 | */ |
| 49 | - List<String> getDictDataLabelList(String dictType); | |
| 59 | + default List<String> getDictDataLabelList(String dictType) { | |
| 60 | + List<DictDataRespDTO> list = getDictDataList(dictType); | |
| 61 | + return convertList(list, DictDataRespDTO::getLabel); | |
| 62 | + } | |
| 50 | 63 | |
| 51 | 64 | } | ... | ... |
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; | |
| 4 | 3 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 5 | 4 | import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; |
| 6 | 5 | import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO; |
| ... | ... | @@ -9,11 +8,8 @@ import jakarta.annotation.Resource; |
| 9 | 8 | import org.springframework.stereotype.Service; |
| 10 | 9 | |
| 11 | 10 | import java.util.Collection; |
| 12 | -import java.util.Collections; | |
| 13 | 11 | import java.util.List; |
| 14 | 12 | |
| 15 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 16 | - | |
| 17 | 13 | /** |
| 18 | 14 | * 字典数据 API 实现类 |
| 19 | 15 | * |
| ... | ... | @@ -43,12 +39,9 @@ public class DictDataApiImpl implements DictDataApi { |
| 43 | 39 | } |
| 44 | 40 | |
| 45 | 41 | @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); | |
| 42 | + public List<DictDataRespDTO> getDictDataList(String dictType) { | |
| 43 | + List<DictDataDO> list = dictDataService.getDictDataListByDictType(dictType); | |
| 44 | + return BeanUtils.toBean(list, DictDataRespDTO.class); | |
| 52 | 45 | } |
| 53 | 46 | |
| 54 | 47 | } | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/ip/AreaController.java
| ... | ... | @@ -29,7 +29,7 @@ public class AreaController { |
| 29 | 29 | @GetMapping("/tree") |
| 30 | 30 | @Operation(summary = "获得地区树") |
| 31 | 31 | public CommonResult<List<AreaNodeRespVO>> getAreaTree() { |
| 32 | - Area area = AreaUtils.getArea(Area.ID_CHINA); | |
| 32 | + Area area = AreaUtils.parseArea(Area.ID_CHINA); | |
| 33 | 33 | Assert.notNull(area, "获取不到中国"); |
| 34 | 34 | return success(BeanUtils.toBean(area.getChildren(), AreaNodeRespVO.class)); |
| 35 | 35 | } | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/ip/AppAreaController.java
| ... | ... | @@ -26,7 +26,7 @@ public class AppAreaController { |
| 26 | 26 | @GetMapping("/tree") |
| 27 | 27 | @Operation(summary = "获得地区树") |
| 28 | 28 | public CommonResult<List<AppAreaNodeRespVO>> getAreaTree() { |
| 29 | - Area area = AreaUtils.getArea(Area.ID_CHINA); | |
| 29 | + Area area = AreaUtils.parseArea(Area.ID_CHINA); | |
| 30 | 30 | Assert.notNull(area, "获取不到中国"); |
| 31 | 31 | return success(BeanUtils.toBean(area.getChildren(), AppAreaNodeRespVO.class)); |
| 32 | 32 | } | ... | ... |
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java
| ... | ... | @@ -171,7 +171,9 @@ public class DictDataServiceImpl implements DictDataService { |
| 171 | 171 | |
| 172 | 172 | @Override |
| 173 | 173 | public List<DictDataDO> getDictDataListByDictType(String dictType) { |
| 174 | - return dictDataMapper.selectList(DictDataDO::getDictType, dictType); | |
| 174 | + List<DictDataDO> list = dictDataMapper.selectList(DictDataDO::getDictType, dictType); | |
| 175 | + list.sort(Comparator.comparing(DictDataDO::getSort)); | |
| 176 | + return list; | |
| 175 | 177 | } |
| 176 | 178 | |
| 177 | 179 | } | ... | ... |