diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java index 8ffd21ccc..cb4ddec34 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/collection/CollectionUtils.java @@ -257,11 +257,11 @@ public class CollectionUtils { return !CollectionUtil.isEmpty(from) ? from.get(0) : null; } - public static T findFirst(List from, Predicate predicate) { + public static T findFirst(Collection from, Predicate predicate) { return findFirst(from, predicate, Function.identity()); } - public static U findFirst(List from, Predicate predicate, Function func) { + public static U findFirst(Collection from, Predicate predicate, Function func) { if (CollUtil.isEmpty(from)) { return null; } diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java index 59656cbdd..87c20798e 100644 --- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java +++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/date/LocalDateTimeUtils.java @@ -132,4 +132,40 @@ public class LocalDateTimeUtils { return LocalDateTimeUtil.between(dateTime, LocalDateTime.now(), ChronoUnit.DAYS); } + /** + * 获取今天的开始时间 + * + * @return 今天 + */ + public static LocalDateTime getToday() { + return LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); + } + + /** + * 获取昨天的开始时间 + * + * @return 昨天 + */ + public static LocalDateTime getYesterday() { + return LocalDateTimeUtil.beginOfDay(LocalDateTime.now().minusDays(1)); + } + + /** + * 获取本月的开始时间 + * + * @return 本月 + */ + public static LocalDateTime getMonth() { + return beginOfMonth(LocalDateTime.now()); + } + + /** + * 获取本年的开始时间 + * + * @return 本年 + */ + public static LocalDateTime getYear() { + return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN); + } + } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java index 5a7340095..99a1ac994 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtils.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.function.Function; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.findFirst; /** * 区域工具类 @@ -70,10 +71,61 @@ public class AreaUtils { * @param id 区域编号 * @return 区域 */ - public static Area getArea(Integer id) { + public static Area parseArea(Integer id) { return areas.get(id); } + /** + * 获得指定区域对应的编号 + * + * @param pathStr 区域路径,例如说:河南省/石家庄市/新华区 + * @return 区域 + */ + public static Area parseArea(String pathStr) { + String[] paths = pathStr.split("/"); + Area area = null; + for (String path : paths) { + if (area == null) { + area = findFirst(areas.values(), item -> item.getName().equals(path)); + } else { + area = findFirst(area.getChildren(), item -> item.getName().equals(path)); + } + } + return area; + } + + /** + * 获取所有节点的全路径名称如:河南省/石家庄市/新华区 + * + * @param areas 地区树 + * @return 所有节点的全路径名称 + */ + public static List getAreaNodePathList(List areas) { + List paths = new ArrayList<>(); + areas.forEach(area -> getAreaNodePathList(area, "", paths)); + return paths; + } + + /** + * 构建一棵树的所有节点的全路径名称,并将其存储为 "祖先/父级/子级" 的形式 + * + * @param node 父节点 + * @param path 全路径名称 + * @param paths 全路径名称列表,省份/城市/地区 + */ + private static void getAreaNodePathList(Area node, String path, List paths) { + if (node == null) { + return; + } + // 构建当前节点的路径 + String currentPath = path.isEmpty() ? node.getName() : path + "/" + node.getName(); + paths.add(currentPath); + // 递归遍历子节点 + for (Area child : node.getChildren()) { + getAreaNodePathList(child, currentPath, paths); + } + } + /** * 格式化区域 * @@ -88,13 +140,13 @@ public class AreaUtils { * 格式化区域 * * 例如说: - * 1. id = “静安区”时:上海 上海市 静安区 - * 2. id = “上海市”时:上海 上海市 - * 3. id = “上海”时:上海 - * 4. id = “美国”时:美国 + * 1. id = “静安区”时:上海 上海市 静安区 + * 2. id = “上海市”时:上海 上海市 + * 3. id = “上海”时:上海 + * 4. id = “美国”时:美国 * 当区域在中国时,默认不显示中国 * - * @param id 区域编号 + * @param id 区域编号 * @param separator 分隔符 * @return 格式化后的区域 */ @@ -141,7 +193,7 @@ public class AreaUtils { */ public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) { for (int i = 0; i < Byte.MAX_VALUE; i++) { - Area area = AreaUtils.getArea(id); + Area area = AreaUtils.parseArea(id); if (area == null) { return null; } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/IPUtils.java b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/IPUtils.java index f74f84864..4701168cd 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/main/java/cn/iocoder/yudao/framework/ip/core/utils/IPUtils.java +++ b/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 { * @return 地区 */ public static Area getArea(String ip) { - return AreaUtils.getArea(getAreaId(ip)); + return AreaUtils.parseArea(getAreaId(ip)); } /** @@ -82,6 +82,6 @@ public class IPUtils { * @return 地区 */ public static Area getArea(long ip) { - return AreaUtils.getArea(getAreaId(ip)); + return AreaUtils.parseArea(getAreaId(ip)); } } diff --git a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/test/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtilsTest.java b/yudao-framework/yudao-spring-boot-starter-biz-ip/src/test/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtilsTest.java index 8f5646b33..704d4415d 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-ip/src/test/java/cn/iocoder/yudao/framework/ip/core/utils/AreaUtilsTest.java +++ b/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 { @Test public void testGetArea() { // 调用:北京 - Area area = AreaUtils.getArea(110100); + Area area = AreaUtils.parseArea(110100); // 断言 assertEquals(area.getId(), 110100); assertEquals(area.getName(), "北京市"); diff --git a/yudao-framework/yudao-spring-boot-starter-excel/pom.xml b/yudao-framework/yudao-spring-boot-starter-excel/pom.xml index 5280f72f7..767532cac 100644 --- a/yudao-framework/yudao-spring-boot-starter-excel/pom.xml +++ b/yudao-framework/yudao-spring-boot-starter-excel/pom.xml @@ -46,6 +46,11 @@ com.alibaba easyexcel + + cn.iocoder.boot + yudao-spring-boot-starter-biz-ip + true + diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java new file mode 100644 index 000000000..9778b17ae --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/convert/AreaConvert.java @@ -0,0 +1,46 @@ +package cn.iocoder.yudao.framework.excel.core.convert; + +import cn.hutool.core.convert.Convert; +import cn.iocoder.yudao.framework.ip.core.Area; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; +import com.alibaba.excel.converters.Converter; +import com.alibaba.excel.enums.CellDataTypeEnum; +import com.alibaba.excel.metadata.GlobalConfiguration; +import com.alibaba.excel.metadata.data.ReadCellData; +import com.alibaba.excel.metadata.property.ExcelContentProperty; +import lombok.extern.slf4j.Slf4j; + +/** + * Excel 数据地区转换器 + * + * @author HUIHUI + */ +@Slf4j +public class AreaConvert implements Converter { + + @Override + public Class supportJavaTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public CellDataTypeEnum supportExcelTypeKey() { + throw new UnsupportedOperationException("暂不支持,也不需要"); + } + + @Override + public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty, + GlobalConfiguration globalConfiguration) { + // 解析地区编号 + String label = readCellData.getStringValue(); + Area area = AreaUtils.parseArea(label); + if (area == null) { + log.error("[convertToJavaData][label({}) 解析不掉]", label); + return null; + } + // 将 value 转换成对应的属性 + Class fieldClazz = contentProperty.getField().getType(); + return Convert.convert(fieldClazz, area.getId()); + } + +} diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/enums/ExcelColumn.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/enums/ExcelColumn.java new file mode 100644 index 000000000..dd8a8374c --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/enums/ExcelColumn.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.framework.excel.core.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +// TODO @puhui999:列表有办法通过 field name 么?主要考虑一个点,可能导入模版的顺序可能会变 +/** + * Excel 列名枚举 + * 默认枚举 26 列列名如果有需求更多的列名请自行补充 + * + * @author HUIHUI + */ +@Getter +@AllArgsConstructor +public enum ExcelColumn { + + A(0), B(1), C(2), D(3), E(4), F(5), G(6), H(7), I(8), + J(9), K(10), L(11), M(12), N(13), O(14), P(15), Q(16), + R(17), S(18), T(19), U(20), V(21), W(22), X(23), Y(24), + Z(25); + + /** + * 列索引 + */ + private final int colNum; + +} diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java new file mode 100644 index 000000000..38d01bd87 --- /dev/null +++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/handler/SelectSheetWriteHandler.java @@ -0,0 +1,115 @@ +package cn.iocoder.yudao.framework.excel.core.handler; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn; +import com.alibaba.excel.write.handler.SheetWriteHandler; +import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; +import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder; +import org.apache.poi.hssf.usermodel.HSSFDataValidation; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.util.CellRangeAddressList; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 基于固定 sheet 实现下拉框 + * + * @author HUIHUI + */ +public class SelectSheetWriteHandler implements SheetWriteHandler { + + /** + * 数据起始行从 0 开始 + * + * 约定:本项目第一行有标题所以从 1 开始如果您的 Excel 有多行标题请自行更改 + */ + public static final int FIRST_ROW = 1; + /** + * 下拉列需要创建下拉框的行数,默认两千行如需更多请自行调整 + */ + public static final int LAST_ROW = 2000; + + private static final String DICT_SHEET_NAME = "字典sheet"; + + // TODO @puhui999:Map> 可以么?之前用 keyvalue 的原因,返回给前端,无法用 linkedhashmap,默认 key 会乱序 + private final List>> selectMap; + + public SelectSheetWriteHandler(List>> selectMap) { + if (CollUtil.isEmpty(selectMap)) { + this.selectMap = null; + return; + } + // 校验一下 key 是否唯一 + Map nameCounts = selectMap.stream() + .collect(Collectors.groupingBy(item -> item.getKey().name(), Collectors.counting())); + Assert.isFalse(nameCounts.entrySet().stream().allMatch(entry -> entry.getValue() > 1), "下拉数据 key 重复请排查!!!"); + + selectMap.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错 + this.selectMap = selectMap; + } + + @Override + public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) { + if (CollUtil.isEmpty(selectMap)) { + return; + } + + // 1. 获取相应操作对象 + DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper(); // 需要设置下拉框的 sheet 页的数据验证助手 + Workbook workbook = writeWorkbookHolder.getWorkbook(); // 获得工作簿 + + // 2. 创建数据字典的 sheet 页 + Sheet dictSheet = workbook.createSheet(DICT_SHEET_NAME); + for (KeyValue> keyValue : selectMap) { + int rowLength = keyValue.getValue().size(); + // 2.1 设置字典 sheet 页的值 每一列一个字典项 + for (int i = 0; i < rowLength; i++) { + Row row = dictSheet.getRow(i); + if (row == null) { + row = dictSheet.createRow(i); + } + row.createCell(keyValue.getKey().getColNum()).setCellValue(keyValue.getValue().get(i)); + } + // 2.2 设置单元格下拉选择 + setColumnSelect(writeSheetHolder, workbook, helper, keyValue); + } + } + + /** + * 设置单元格下拉选择 + */ + private static void setColumnSelect(WriteSheetHolder writeSheetHolder, Workbook workbook, DataValidationHelper helper, + KeyValue> keyValue) { + // 1.1 创建可被其他单元格引用的名称 + Name name = workbook.createName(); + String excelColumn = keyValue.getKey().name(); + // 1.2 下拉框数据来源 eg:字典sheet!$B1:$B2 + String refers = DICT_SHEET_NAME + "!$" + excelColumn + "$1:$" + excelColumn + "$" + keyValue.getValue().size(); + name.setNameName("dict" + keyValue.getKey()); // 设置名称的名字 + name.setRefersToFormula(refers); // 设置公式 + + // 2.1 设置约束 + DataValidationConstraint constraint = helper.createFormulaListConstraint("dict" + keyValue.getKey()); // 设置引用约束 + // 设置下拉单元格的首行、末行、首列、末列 + CellRangeAddressList rangeAddressList = new CellRangeAddressList(FIRST_ROW, LAST_ROW, + keyValue.getKey().getColNum(), keyValue.getKey().getColNum()); + DataValidation validation = helper.createValidation(constraint, rangeAddressList); + if (validation instanceof HSSFDataValidation) { + validation.setSuppressDropDownArrow(false); + } else { + validation.setSuppressDropDownArrow(true); + validation.setShowErrorBox(true); + } + // 2.2 阻止输入非下拉框的值 + validation.setErrorStyle(DataValidation.ErrorStyle.STOP); + validation.createErrorBox("提示", "此值不存在于下拉选择中!"); + // 2.3 添加下拉框约束 + writeSheetHolder.getSheet().addValidationData(validation); + } + +} \ No newline at end of file diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java index c2104693e..81d9a8a97 100644 --- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java +++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java @@ -1,11 +1,14 @@ package cn.iocoder.yudao.framework.excel.core.util; +import cn.iocoder.yudao.framework.common.core.KeyValue; +import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn; +import cn.iocoder.yudao.framework.excel.core.handler.SelectSheetWriteHandler; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.converters.longconverter.LongStringConverter; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; +import jakarta.servlet.http.HttpServletResponse; import org.springframework.web.multipart.MultipartFile; -import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -21,29 +24,46 @@ public class ExcelUtils { /** * 将列表以 Excel 响应给前端 * - * @param response 响应 - * @param filename 文件名 + * @param response 响应 + * @param filename 文件名 * @param sheetName Excel sheet 名 - * @param head Excel head 头 - * @param data 数据列表哦 - * @param 泛型,保证 head 和 data 类型的一致性 + * @param head Excel head 头 + * @param data 数据列表哦 + * @param 泛型,保证 head 和 data 类型的一致性 * @throws IOException 写入失败的情况 */ public static void write(HttpServletResponse response, String filename, String sheetName, Class head, List data) throws IOException { + write(response, filename, sheetName, head, data, null); + } + + /** + * 将列表以 Excel 响应给前端 + * + * @param response 响应 + * @param filename 文件名 + * @param sheetName Excel sheet 名 + * @param head Excel head 头 + * @param data 数据列表哦 + * @param selectMap 下拉选择数据 Map<下拉所对应的列表名,下拉数据> + * @throws IOException 写入失败的情况 + */ + public static void write(HttpServletResponse response, String filename, String sheetName, + Class head, List data, List>> selectMap) throws IOException { // 输出 Excel EasyExcel.write(response.getOutputStream(), head) .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度,自动适配。最大 255 宽度 + .registerWriteHandler(new SelectSheetWriteHandler(selectMap)) // 基于固定 sheet 实现下拉框 .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度 .sheet(sheetName).doWrite(data); // 设置 header 和 contentType。写在最后的原因是,避免报错时,响应 contentType 已经被修改了 - response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8)); + response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name())); response.setContentType("application/vnd.ms-excel;charset=UTF-8"); } public static List read(MultipartFile file, Class head) throws IOException { - return EasyExcel.read(file.getInputStream(), head, null) + return EasyExcel.read(file.getInputStream(), head, null) .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 .doReadAllSync(); } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/listener/BpmResultListenerApi.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/listener/BpmResultListenerApi.java deleted file mode 100644 index d519ed9ed..000000000 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/listener/BpmResultListenerApi.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.bpm.api.listener; - -import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO; - -// TODO @芋艿:后续改成支持 RPC -/** - * 业务流程实例的结果发生变化的监听器 Api - * - * @author HUIHUI - */ -public interface BpmResultListenerApi { - - /** - * 监听的流程定义 Key - * - * @return 返回监听的流程定义 Key - */ - String getProcessDefinitionKey(); - - /** - * 处理事件 - * - * @param event 事件 - */ - void onEvent(BpmResultListenerRespDTO event); - -} diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/listener/dto/BpmResultListenerRespDTO.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/listener/dto/BpmResultListenerRespDTO.java deleted file mode 100644 index 30721785d..000000000 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/listener/dto/BpmResultListenerRespDTO.java +++ /dev/null @@ -1,32 +0,0 @@ -package cn.iocoder.yudao.module.bpm.api.listener.dto; - -import lombok.Data; - -// TODO @芋艿:后续改成支持 RPC -/** - * 业务流程实例的结果 Response DTO - * - * @author HUIHUI - */ -@Data -public class BpmResultListenerRespDTO { - - /** - * 流程实例的编号 - */ - private String id; - /** - * 流程实例的 key - */ - private String processDefinitionKey; - /** - * 流程实例的结果 - */ - private Integer result; - /** - * 流程实例对应的业务标识 - * 例如说,请假 - */ - private String businessKey; - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEvent.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/event/BpmProcessInstanceResultEvent.java similarity index 87% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEvent.java rename to yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/event/BpmProcessInstanceResultEvent.java index f94f20ac3..9b703728d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEvent.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/event/BpmProcessInstanceResultEvent.java @@ -1,10 +1,8 @@ -package cn.iocoder.yudao.module.bpm.framework.bpm.core.event; - -import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; -import lombok.Data; -import org.springframework.context.ApplicationEvent; +package cn.iocoder.yudao.module.bpm.event; import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.springframework.context.ApplicationEvent; /** * 流程实例的结果发生变化的 Event diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventListener.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/event/BpmProcessInstanceResultEventListener.java similarity index 93% rename from yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventListener.java rename to yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/event/BpmProcessInstanceResultEventListener.java index c2b215394..bff99a8c1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventListener.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/event/BpmProcessInstanceResultEventListener.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.bpm.framework.bpm.core.event; +package cn.iocoder.yudao.module.bpm.event; import cn.hutool.core.util.StrUtil; import org.springframework.context.ApplicationListener; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java index 8296b8bb3..41f2184c5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java @@ -7,7 +7,7 @@ import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessI import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceRespVO; import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionExtDO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceExtDO; -import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent; +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO; import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventPublisher.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventPublisher.java index 05434f7c2..b759fe1e1 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventPublisher.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/core/event/BpmProcessInstanceResultEventPublisher.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.framework.bpm.core.event; +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent; import lombok.AllArgsConstructor; import org.springframework.context.ApplicationEventPublisher; import org.springframework.validation.annotation.Validated; diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/listener/BpmServiceResultListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/listener/BpmServiceResultListener.java deleted file mode 100644 index 34a045a23..000000000 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/bpm/listener/BpmServiceResultListener.java +++ /dev/null @@ -1,36 +0,0 @@ -package cn.iocoder.yudao.module.bpm.framework.bpm.listener; - -import cn.hutool.core.util.StrUtil; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.bpm.api.listener.BpmResultListenerApi; -import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO; -import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent; -import jakarta.annotation.Resource; -import org.springframework.context.ApplicationListener; -import org.springframework.stereotype.Component; - -import java.util.List; - -// TODO @芋艿:后续改成支持 RPC -/** - * 业务流程结果监听器实现类 - * - * @author HUIHUI - */ -@Component -public class BpmServiceResultListener implements ApplicationListener { - - @Resource - private List bpmResultListenerApis; - - @Override - public final void onApplicationEvent(BpmProcessInstanceResultEvent event) { - bpmResultListenerApis.forEach(bpmResultListenerApi -> { - if (!StrUtil.equals(event.getProcessDefinitionKey(), bpmResultListenerApi.getProcessDefinitionKey())) { - return; - } - bpmResultListenerApi.onEvent(BeanUtils.toBean(event, BpmResultListenerRespDTO.class)); - }); - } - -} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/listener/BpmOALeaveResultListener.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/listener/BpmOALeaveResultListener.java index 0f8e9e569..e1dccb52b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/listener/BpmOALeaveResultListener.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/oa/listener/BpmOALeaveResultListener.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.bpm.service.oa.listener; -import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEvent; -import cn.iocoder.yudao.module.bpm.framework.bpm.core.event.BpmProcessInstanceResultEventListener; +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent; +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEventListener; import cn.iocoder.yudao.module.bpm.service.oa.BpmOALeaveService; import cn.iocoder.yudao.module.bpm.service.oa.BpmOALeaveServiceImpl; import org.springframework.stereotype.Component; diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java index 22bb0b426..eee2b32d9 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/DictTypeConstants.java @@ -14,5 +14,6 @@ public interface DictTypeConstants { String CRM_PRODUCT_UNIT = "crm_product_unit"; // CRM 产品单位 String CRM_PRODUCT_STATUS = "crm_product_status"; // CRM 产品状态 String CRM_FOLLOW_UP_TYPE = "crm_follow_up_type"; // CRM 跟进方式 + String CRM_RECEIVABLE_RETURN_TYPE = "crm_receivable_return_type"; // CRM 回款方式 } diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java index d536d8a40..a15a3211b 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java @@ -11,31 +11,40 @@ public interface ErrorCodeConstants { // ========== 合同管理 1-020-000-000 ========== ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在"); - ErrorCode CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_000_001, "更新合同失败,原因:禁止编辑"); + ErrorCode CONTRACT_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_001, "合同更新失败,原因:合同不是草稿状态"); ErrorCode CONTRACT_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_000_002, "合同提交审核失败,原因:合同没处在未提交状态"); + ErrorCode CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_000_003, "更新合同审核状态失败,原因:合同不是审核中状态"); + ErrorCode CONTRACT_NO_EXISTS = new ErrorCode(1_020_000_004, "生成合同序列号重复,请重试"); // ========== 线索管理 1-020-001-000 ========== ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在"); - ErrorCode CLUE_ANY_CLUE_NOT_EXISTS = new ErrorCode(1_020_001_001, "线索【{}】不存在"); - ErrorCode CLUE_ANY_CLUE_ALREADY_TRANSLATED = new ErrorCode(1_020_001_002, "线索【{}】已经转化过了,请勿重复转化"); + ErrorCode CLUE_TRANSFORM_FAIL_ALREADY = new ErrorCode(1_020_001_001, "线索已经转化过了,请勿重复转化"); // ========== 商机管理 1-020-002-000 ========== ErrorCode BUSINESS_NOT_EXISTS = new ErrorCode(1_020_002_000, "商机不存在"); - ErrorCode BUSINESS_CONTRACT_EXISTS = new ErrorCode(1_020_002_001, "商机已关联合同,不能删除"); - - // TODO @lilleo:商机状态、商机类型,都单独错误码段 - + ErrorCode BUSINESS_DELETE_FAIL_CONTRACT_EXISTS = new ErrorCode(1_020_002_001, "商机已关联合同,不能删除"); + ErrorCode BUSINESS_UPDATE_STATUS_FAIL_END_STATUS = new ErrorCode(1_020_002_002, "更新商机状态失败,原因:已经是结束状态"); + ErrorCode BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS = new ErrorCode(1_020_002_003, "更新商机状态失败,原因:已经是该状态"); // ========== 联系人管理 1-020-003-000 ========== ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在"); - ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode(1_020_003_001, "联系人商机关联不存在"); ErrorCode CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS = new ErrorCode(1_020_003_002, "联系人已关联合同,不能删除"); + ErrorCode CONTACT_UPDATE_OWNER_USER_FAIL = new ErrorCode(1_020_003_003, "更新联系人负责人失败"); // ========== 回款 1-020-004-000 ========== ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在"); + ErrorCode RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_004_001, "更新回款失败,原因:禁止编辑"); + ErrorCode RECEIVABLE_DELETE_FAIL = new ErrorCode(1_020_004_002, "删除回款失败,原因: 被回款计划所使用,不允许删除"); + ErrorCode RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_020_004_003, "回款提交审核失败,原因:回款没处在未提交状态"); + ErrorCode RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_020_004_004, "更新回款审核状态失败,原因:回款不是审核中状态"); + ErrorCode RECEIVABLE_NO_EXISTS = new ErrorCode(1_020_004_005, "生成回款序列号重复,请重试"); + ErrorCode RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE = new ErrorCode(1_020_004_006, "创建回款失败,原因:合同不是审核通过状态"); + ErrorCode RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_020_004_007, "创建回款失败,原因:回款金额超出合同金额,目前剩余可退:{} 元"); - // ========== 合同管理 1-020-005-000 ========== + // ========== 回款计划 1-020-005-000 ========== ErrorCode RECEIVABLE_PLAN_NOT_EXISTS = new ErrorCode(1_020_005_000, "回款计划不存在"); + ErrorCode RECEIVABLE_PLAN_UPDATE_FAIL = new ErrorCode(1_020_006_000, "更想回款计划失败,原因:已经有对应的还款"); + ErrorCode RECEIVABLE_PLAN_EXISTS_RECEIVABLE = new ErrorCode(1_020_006_001, "回款计划已经有对应的回款,不能使用"); // ========== 客户管理 1_020_006_000 ========== ErrorCode CUSTOMER_NOT_EXISTS = new ErrorCode(1_020_006_000, "客户不存在"); @@ -53,14 +62,13 @@ public interface ErrorCodeConstants { ErrorCode CUSTOMER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_020_006_012, "导入客户数据不能为空!"); ErrorCode CUSTOMER_CREATE_NAME_NOT_NULL = new ErrorCode(1_020_006_013, "客户名称不能为空!"); ErrorCode CUSTOMER_NAME_EXISTS = new ErrorCode(1_020_006_014, "已存在名为【{}】的客户!"); + ErrorCode CUSTOMER_UPDATE_DEAL_STATUS_FAIL = new ErrorCode(1_020_006_015, "更新客户的成交状态失败,原因:已经是该状态,无需更新"); // ========== 权限管理 1_020_007_000 ========== ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在"); ErrorCode CRM_PERMISSION_DENIED = new ErrorCode(1_020_007_001, "{}操作失败,原因:没有权限"); - ErrorCode CRM_PERMISSION_MODEL_NOT_EXISTS = new ErrorCode(1_020_007_002, "{}不存在"); ErrorCode CRM_PERMISSION_MODEL_TRANSFER_FAIL_OWNER_USER_EXISTS = new ErrorCode(1_020_007_003, "{}操作失败,原因:转移对象已经是该负责人"); ErrorCode CRM_PERMISSION_DELETE_FAIL = new ErrorCode(1_020_007_004, "删除数据权限失败,原因:批量删除权限的时候,只能属于同一个 bizId 下"); - ErrorCode CRM_PERMISSION_DELETE_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_005, "删除数据权限失败,原因:存在负责人"); ErrorCode CRM_PERMISSION_DELETE_DENIED = new ErrorCode(1_020_007_006, "删除数据权限失败,原因:没有权限"); ErrorCode CRM_PERMISSION_DELETE_SELF_PERMISSION_FAIL_EXIST_OWNER = new ErrorCode(1_020_007_007, "删除数据权限失败,原因:不能删除负责人"); ErrorCode CRM_PERMISSION_CREATE_FAIL = new ErrorCode(1_020_007_008, "创建数据权限失败,原因:所加用户已有权限"); @@ -68,6 +76,7 @@ public interface ErrorCodeConstants { // ========== 产品 1_020_008_000 ========== ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1_020_008_000, "产品不存在"); ErrorCode PRODUCT_NO_EXISTS = new ErrorCode(1_020_008_001, "产品编号已存在"); + ErrorCode PRODUCT_NOT_ENABLE = new ErrorCode(1_020_008_002, "产品【{}】已禁用"); // ========== 产品分类 1_020_009_000 ========== ErrorCode PRODUCT_CATEGORY_NOT_EXISTS = new ErrorCode(1_020_009_000, "产品分类不存在"); @@ -77,21 +86,18 @@ public interface ErrorCodeConstants { ErrorCode PRODUCT_CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1_020_009_004, "父分类不能是二级分类"); ErrorCode product_CATEGORY_EXISTS_CHILDREN = new ErrorCode(1_020_009_005, "存在子分类,无法删除"); - // ========== 商机状态类型 1_020_010_000 ========== - ErrorCode BUSINESS_STATUS_TYPE_NOT_EXISTS = new ErrorCode(1_020_010_000, "商机状态类型不存在"); - ErrorCode BUSINESS_STATUS_TYPE_NAME_EXISTS = new ErrorCode(1_020_010_001, "商机状态类型名称已存在"); - - // ========== 商机状态 1_020_011_000 ========== - ErrorCode BUSINESS_STATUS_NOT_EXISTS = new ErrorCode(1_020_011_000, "商机状态不存在"); + // ========== 商机状态 1_020_010_000 ========== + ErrorCode BUSINESS_STATUS_TYPE_NOT_EXISTS = new ErrorCode(1_020_010_000, "商机状态组不存在"); + ErrorCode BUSINESS_STATUS_TYPE_NAME_EXISTS = new ErrorCode(1_020_010_001, "商机状态组的名称已存在"); + ErrorCode BUSINESS_STATUS_UPDATE_FAIL_USED = new ErrorCode(1_020_010_002, "已经被使用的商机状态组,无法进行更新"); + ErrorCode BUSINESS_STATUS_DELETE_FAIL_USED = new ErrorCode(1_020_010_002, "已经被使用的商机状态组,无法进行删除"); + ErrorCode BUSINESS_STATUS_NOT_EXISTS = new ErrorCode(1_020_010_003, "商机状态不存在"); // ========== 客户公海规则设置 1_020_012_000 ========== - ErrorCode CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED = new ErrorCode(1_020_012_000, "客户公海配置不存在或未启用"); ErrorCode CUSTOMER_LIMIT_CONFIG_NOT_EXISTS = new ErrorCode(1_020_012_001, "客户限制配置不存在"); // ========== 跟进记录 1_020_013_000 ========== ErrorCode FOLLOW_UP_RECORD_NOT_EXISTS = new ErrorCode(1_020_013_000, "跟进记录不存在"); ErrorCode FOLLOW_UP_RECORD_DELETE_DENIED = new ErrorCode(1_020_013_001, "删除跟进记录失败,原因:没有权限"); - // ========== 待办消息 1_020_014_000 ========== - } diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java index 98a66d2c9..c2c835b7f 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java @@ -8,19 +8,21 @@ package cn.iocoder.yudao.module.crm.enums; */ public interface LogRecordConstants { - // ======================= CRM_LEADS 线索 ======================= + // ======================= CRM_CLUE 线索 ======================= - String CRM_LEADS_TYPE = "CRM 线索"; - String CRM_LEADS_CREATE_SUB_TYPE = "创建线索"; - String CRM_LEADS_CREATE_SUCCESS = "创建了线索{{#clue.name}}"; - String CRM_LEADS_UPDATE_SUB_TYPE = "更新线索"; - String CRM_LEADS_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}"; - String CRM_LEADS_DELETE_SUB_TYPE = "删除线索"; - String CRM_LEADS_DELETE_SUCCESS = "删除了线索【{{#clueName}}】"; - String CRM_LEADS_TRANSFER_SUB_TYPE = "转移线索"; - String CRM_LEADS_TRANSFER_SUCCESS = "将线索【{{#clue.name}}】的负责人从【{getAdminUserById{#clue.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; - String CRM_LEADS_TRANSLATE_SUB_TYPE = "线索转化为客户"; - String CRM_LEADS_TRANSLATE_SUCCESS = "将线索【{{#clue.name}}】转化为客户"; + String CRM_CLUE_TYPE = "CRM 线索"; + String CRM_CLUE_CREATE_SUB_TYPE = "创建线索"; + String CRM_CLUE_CREATE_SUCCESS = "创建了线索{{#clue.name}}"; + String CRM_CLUE_UPDATE_SUB_TYPE = "更新线索"; + String CRM_CLUE_UPDATE_SUCCESS = "更新了线索【{{#clueName}}】: {_DIFF{#updateReq}}"; + String CRM_CLUE_DELETE_SUB_TYPE = "删除线索"; + String CRM_CLUE_DELETE_SUCCESS = "删除了线索【{{#clueName}}】"; + String CRM_CLUE_TRANSFER_SUB_TYPE = "转移线索"; + String CRM_CLUE_TRANSFER_SUCCESS = "将线索【{{#clue.name}}】的负责人从【{getAdminUserById{#clue.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + String CRM_CLUE_TRANSLATE_SUB_TYPE = "线索转化为客户"; + String CRM_CLUE_TRANSLATE_SUCCESS = "将线索【{{#clueName}}】转化为客户"; + String CRM_CLUE_FOLLOW_UP_SUB_TYPE = "线索跟进"; + String CRM_CLUE_FOLLOW_UP_SUCCESS = "线索跟进【{{#clueName}}】"; // ======================= CRM_CUSTOMER 客户 ======================= @@ -41,6 +43,10 @@ public interface LogRecordConstants { String CRM_CUSTOMER_RECEIVE_SUCCESS = "{{#ownerUserName != null ? '将客户【' + #customer.name + '】分配给【' + #ownerUserName + '】' : '领取客户【' + #customer.name + '】'}}"; String CRM_CUSTOMER_IMPORT_SUB_TYPE = "{{#isUpdate ? '导入并更新客户' : '导入客户'}}"; String CRM_CUSTOMER_IMPORT_SUCCESS = "{{#isUpdate ? '导入并更新了客户【'+ #customer.name +'】' : '导入了客户【'+ #customer.name +'】'}}"; + String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUB_TYPE = "更新客户成交状态"; + String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUCCESS = "更新了客户【{{#customerName}}】的成交状态为【{{#dealStatus ? '已成交' : '未成交'}}】"; + String CRM_CUSTOMER_FOLLOW_UP_SUB_TYPE = "客户跟进"; + String CRM_CUSTOMER_FOLLOW_UP_SUCCESS = "客户跟进【{{#customerName}}】"; // ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 ======================= @@ -69,6 +75,10 @@ public interface LogRecordConstants { String CRM_CONTACT_DELETE_SUCCESS = "删除了联系人【{{#contactName}}】"; String CRM_CONTACT_TRANSFER_SUB_TYPE = "转移联系人"; String CRM_CONTACT_TRANSFER_SUCCESS = "将联系人【{{#contact.name}}】的负责人从【{getAdminUserById{#contact.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + String CRM_CONTACT_FOLLOW_UP_SUB_TYPE = "联系人跟进"; + String CRM_CONTACT_FOLLOW_UP_SUCCESS = "联系人跟进【{{#contactName}}】"; + String CRM_CONTACT_UPDATE_OWNER_USER_SUB_TYPE = "更新联系人负责人"; + String CRM_CONTACT_UPDATE_OWNER_USER_SUCCESS = "将联系人【{{#contact.name}}】的负责人从【{getAdminUserById{#contact.ownerUserId}}】变更为了【{getAdminUserById{#ownerUserId}}】"; // ======================= CRM_BUSINESS 商机 ======================= @@ -81,6 +91,16 @@ public interface LogRecordConstants { String CRM_BUSINESS_DELETE_SUCCESS = "删除了商机【{{#businessName}}】"; String CRM_BUSINESS_TRANSFER_SUB_TYPE = "转移商机"; String CRM_BUSINESS_TRANSFER_SUCCESS = "将商机【{{#business.name}}】的负责人从【{getAdminUserById{#business.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; + String CRM_BUSINESS_FOLLOW_UP_SUB_TYPE = "商机跟进"; + String CRM_BUSINESS_FOLLOW_UP_SUCCESS = "商机跟进【{{#businessName}}】"; + String CRM_BUSINESS_UPDATE_STATUS_SUB_TYPE = "更新商机状态"; + String CRM_BUSINESS_UPDATE_STATUS_SUCCESS = "更新了商机【{{#businessName}}】的状态从【{{#oldStatusName}}】变更为了【{{#newStatusName}}】"; + + // ======================= CRM_CONTRACT_CONFIG 合同配置 ======================= + + String CRM_CONTRACT_CONFIG_TYPE = "CRM 合同配置"; + String CRM_CONTRACT_CONFIG_SUB_TYPE = "{{#isPoolConfigUpdate ? '更新合同配置' : '创建合同配置'}}"; + String CRM_CONTRACT_CONFIG_SUCCESS = "{{#isPoolConfigUpdate ? '更新了合同配置' : '创建了合同配置'}}"; // ======================= CRM_CONTRACT 合同 ======================= @@ -95,6 +115,8 @@ public interface LogRecordConstants { String CRM_CONTRACT_TRANSFER_SUCCESS = "将合同【{{#contract.name}}】的负责人从【{getAdminUserById{#contract.ownerUserId}}】变更为了【{getAdminUserById{#reqVO.newOwnerUserId}}】"; String CRM_CONTRACT_SUBMIT_SUB_TYPE = "提交合同审批"; String CRM_CONTRACT_SUBMIT_SUCCESS = "提交合同【{{#contractName}}】审批成功"; + String CRM_CONTRACT_FOLLOW_UP_SUB_TYPE = "合同跟进"; + String CRM_CONTRACT_FOLLOW_UP_SUCCESS = "合同跟进【{{#contractName}}】"; // ======================= CRM_PRODUCT 产品 ======================= @@ -125,6 +147,8 @@ public interface LogRecordConstants { String CRM_RECEIVABLE_UPDATE_SUCCESS = "更新了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款: {_DIFF{#updateReqVO}}"; String CRM_RECEIVABLE_DELETE_SUB_TYPE = "删除回款"; String CRM_RECEIVABLE_DELETE_SUCCESS = "删除了合同【{getContractById{#receivable.contractId}}】的第【{{#receivable.period}}】期回款"; + String CRM_RECEIVABLE_SUBMIT_SUB_TYPE = "提交回款审批"; + String CRM_RECEIVABLE_SUBMIT_SUCCESS = "提交编号为【{{#receivableNo}}】的回款审批成功"; // ======================= CRM_RECEIVABLE_PLAN 回款计划 ======================= diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java deleted file mode 100644 index 55548dbff..000000000 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBizEndStatus.java +++ /dev/null @@ -1,55 +0,0 @@ -package cn.iocoder.yudao.module.crm.enums.business; - -import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.framework.common.core.IntArrayValuable; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -import java.util.Arrays; - -// TODO @lzxhqs:1)title、description、create 可以删除,非标准的 javadoc 注释哈,然后可以在类上加下这个类的注释;2)CrmBizEndStatus 改成 CrmBusinessEndStatus,非必要不缩写哈,可阅读比较重要 -/** - * @author lzxhqs - * @version 1.0 - * @title CrmBizEndStatus - * @description - * @create 2024/1/12 - */ -@RequiredArgsConstructor -@Getter -public enum CrmBizEndStatus implements IntArrayValuable { - - WIN(1, "赢单"), - LOSE(2, "输单"), - INVALID(3, "无效"); - - public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmBizEndStatus::getStatus).toArray(); - - // TODO @lzxhqs:这里的方法,建议放到 49 行之后;一般类里是,静态变量,普通变量;静态方法;普通方法 - public static boolean isWin(Integer status) { - return ObjectUtil.equal(WIN.getStatus(), status); - } - - public static boolean isLose(Integer status) { - return ObjectUtil.equal(LOSE.getStatus(), status); - } - - public static boolean isInvalid(Integer status) { - return ObjectUtil.equal(INVALID.getStatus(), status); - } - - /** - * 场景类型 - */ - private final Integer status; - /** - * 场景名称 - */ - private final String name; - - @Override - public int[] array() { - return ARRAYS; - } - -} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBusinessEndStatusEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBusinessEndStatusEnum.java new file mode 100644 index 000000000..4736c01b7 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/business/CrmBusinessEndStatusEnum.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.crm.enums.business; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; + +/** + * 商机的结束状态枚举 + * + * @author lzxhqs + */ +@RequiredArgsConstructor +@Getter +public enum CrmBusinessEndStatusEnum implements IntArrayValuable { + + WIN(1, "赢单"), + LOSE(2, "输单"), + INVALID(3, "无效"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmBusinessEndStatusEnum::getStatus).toArray(); + + /** + * 场景类型 + */ + private final Integer status; + /** + * 场景名称 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + + public static CrmBusinessEndStatusEnum fromStatus(Integer status) { + return Arrays.stream(values()) + .filter(value -> value.getStatus().equals(status)) + .findFirst() + .orElse(null); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java index f0784cab2..8402ad288 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/common/CrmBizTypeEnum.java @@ -17,7 +17,7 @@ import java.util.Arrays; @Getter public enum CrmBizTypeEnum implements IntArrayValuable { - CRM_LEADS(1, "线索"), + CRM_CLUE(1, "线索"), CRM_CUSTOMER(2, "客户"), CRM_CONTACT(3, "联系人"), CRM_BUSINESS(4, "商机"), diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionLevelEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionLevelEnum.java index 56b0366aa..7b44fe962 100644 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionLevelEnum.java +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionLevelEnum.java @@ -10,6 +10,8 @@ import java.util.Arrays; /** * CRM 数据权限级别枚举 * + * OWNER > WRITE > READ + * * @author HUIHUI */ @Getter @@ -17,8 +19,8 @@ import java.util.Arrays; public enum CrmPermissionLevelEnum implements IntArrayValuable { OWNER(1, "负责人"), - READ(2, "读"), - WRITE(3, "写"); + READ(2, "只读"), + WRITE(3, "读写"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmPermissionLevelEnum::getLevel).toArray(); diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java deleted file mode 100644 index c9a51057b..000000000 --- a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/permission/CrmPermissionRoleCodeEnum.java +++ /dev/null @@ -1,27 +0,0 @@ -package cn.iocoder.yudao.module.crm.enums.permission; - -import lombok.AllArgsConstructor; -import lombok.Getter; - -/** - * Crm 数据权限角色枚举 - * - * @author HUIHUI - */ -@Getter -@AllArgsConstructor -public enum CrmPermissionRoleCodeEnum { - - CRM_ADMIN("crm_admin", "CRM 管理员"); - - /** - * 角色标识 - */ - private String code; - /** - * 角色名称 - */ - private String name; - -} - diff --git a/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/receivable/CrmReceivableReturnTypeEnum.java b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/receivable/CrmReceivableReturnTypeEnum.java new file mode 100644 index 000000000..3c01fe95c --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/receivable/CrmReceivableReturnTypeEnum.java @@ -0,0 +1,43 @@ +package cn.iocoder.yudao.module.crm.enums.receivable; + +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; + +/** + * CRM 回款方式枚举 + * + * @author HUIHUI + */ +@Getter +@AllArgsConstructor +public enum CrmReceivableReturnTypeEnum implements IntArrayValuable { + + CHECK(1, "支票"), + CASH(2, "现金"), + POSTAL_REMITTANCE(3, "邮政汇款"), + TELEGRAPHIC_TRANSFER(4, "电汇"), + ONLINE_TRANSFER(5, "网上转账"), + ALIPAY(6, "支付宝"), + WECHAT_PAY(7, "微信支付"), + OTHER(8, "其它"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CrmReceivableReturnTypeEnum::getType).toArray(); + + /** + * 类型 + */ + private final Integer type; + /** + * 名称 + */ + private final String name; + + @Override + public int[] array() { + return ARRAYS; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/CrmBacklogController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/CrmBacklogController.java deleted file mode 100644 index 9b8841e2e..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/CrmBacklogController.java +++ /dev/null @@ -1,41 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.backlog; - -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.backlog.vo.CrmTodayCustomerPageReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.service.message.CrmBacklogService; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -@Tag(name = "管理后台 - CRM待办消息") -@RestController -@RequestMapping("/crm/backlog") -@Validated -public class CrmBacklogController { - - @Resource - private CrmBacklogService crmMessageService; - - // TODO 芋艿:未来可能合并到 CrmCustomerController - @GetMapping("/today-customer-page") - @Operation(summary = "今日需联系客户") - @PreAuthorize("@ss.hasPermission('crm:customer:query')") - public CommonResult> getTodayCustomerPage(@Valid CrmTodayCustomerPageReqVO pageReqVO) { - PageResult pageResult = crmMessageService.getTodayCustomerPage(pageReqVO, getLoginUserId()); - return success(BeanUtils.toBean(pageResult, CrmCustomerRespVO.class)); - } - -} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/vo/CrmTodayCustomerPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/vo/CrmTodayCustomerPageReqVO.java deleted file mode 100644 index 21fd88c0b..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/backlog/vo/CrmTodayCustomerPageReqVO.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.backlog.vo; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -@Schema(description = "管理后台 - 今日需联系客户 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmTodayCustomerPageReqVO extends PageParam { - - /** - * 联系状态 - 今日需联系 - */ - public static final int CONTACT_TODAY = 1; - /** - * 联系状态 - 已逾期 - */ - public static final int CONTACT_EXPIRED = 2; - /** - * 联系状态 - 已联系 - */ - public static final int CONTACT_ALREADY = 3; - - @Schema(description = "联系状态", example = "1") - private Integer contactStatus; - - @Schema(description = "场景类型", example = "1") - @InEnum(CrmSceneTypeEnum.class) - private Integer sceneType; - -} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.http b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.http deleted file mode 100644 index b9e9a4edf..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.http +++ /dev/null @@ -1,9 +0,0 @@ -### 合同金额排行榜 -GET {{baseUrl}}/crm/bi-rank/get-contract-price-rank?deptId=100×[0]=2022-12-12 00:00:00×[1]=2024-12-12 23:59:59 -Authorization: Bearer {{token}} -tenant-id: {{adminTenentId}} - -### 回款金额排行榜 -GET {{baseUrl}}/crm/bi-rank/get-receivable-price-rank?deptId=100×[0]=2022-12-12 00:00:00×[1]=2024-12-12 23:59:59 -Authorization: Bearer {{token}} -tenant-id: {{adminTenentId}} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.http b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.http deleted file mode 100644 index 55adb4bd5..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.http +++ /dev/null @@ -1,32 +0,0 @@ -### 请求 /transfer -PUT {{baseUrl}}/crm/business/transfer -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenentId}} - -{ - "id": 1, - "ownerUserId": 2, - "transferType": 2, - "permissionType": 2 -} - -### 请求 /update -PUT {{baseUrl}}/crm/business/update -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenentId}} - -{ - "id": 1, - "name": "2", - "statusTypeId": 2, - "statusId": 2, - "customerId": 1 -} - -### 请求 /get -GET {{baseUrl}}/crm/business/get?id=1024 -Content-Type: application/json -Authorization: Bearer {{token}} -tenant-id: {{adminTenentId}} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java index c85c151f5..505c0ec46 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java @@ -3,22 +3,26 @@ package cn.iocoder.yudao.module.crm.controller.admin.business; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; -import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.*; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService; -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusTypeService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.crm.service.product.CrmProductService; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -30,13 +34,15 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_NOT_EXISTS; @@ -52,9 +58,16 @@ public class CrmBusinessController { @Resource private CrmCustomerService customerService; @Resource - private CrmBusinessStatusTypeService businessStatusTypeService; + private CrmBusinessStatusService businessStatusTypeService; @Resource private CrmBusinessStatusService businessStatusService; + @Resource + private CrmProductService productService; + + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; @PostMapping("/create") @Operation(summary = "创建商机") @@ -71,6 +84,14 @@ public class CrmBusinessController { return success(true); } + @PutMapping("/update-status") + @Operation(summary = "更新商机状态") + @PreAuthorize("@ss.hasPermission('crm:business:update')") + public CommonResult updateBusinessStatus(@Valid @RequestBody CrmBusinessUpdateStatusReqVO updateStatusReqVO) { + businessService.updateBusinessStatus(updateStatusReqVO); + return success(true); + } + @DeleteMapping("/delete") @Operation(summary = "删除商机") @Parameter(name = "id", description = "编号", required = true) @@ -86,15 +107,23 @@ public class CrmBusinessController { @PreAuthorize("@ss.hasPermission('crm:business:query')") public CommonResult getBusiness(@RequestParam("id") Long id) { CrmBusinessDO business = businessService.getBusiness(id); - return success(BeanUtils.toBean(business, CrmBusinessRespVO.class)); + return success(buildBusinessDetail(business)); } - @GetMapping("/list-by-ids") - @Operation(summary = "获得商机列表") - @Parameter(name = "ids", description = "编号", required = true, example = "[1024]") - @PreAuthorize("@ss.hasPermission('crm:business:query')") - public CommonResult> getContactListByIds(@RequestParam("ids") List ids) { - return success(BeanUtils.toBean(businessService.getBusinessList(ids, getLoginUserId()), CrmBusinessRespVO.class)); + private CrmBusinessRespVO buildBusinessDetail(CrmBusinessDO business) { + if (business == null) { + return null; + } + CrmBusinessRespVO businessVO = buildBusinessDetailList(Collections.singletonList(business)).get(0); + // 拼接产品项 + List businessProducts = businessService.getBusinessProductListByBusinessId(businessVO.getId()); + Map productMap = productService.getProductMap( + convertSet(businessProducts, CrmBusinessProductDO::getProductId)); + businessVO.setProducts(BeanUtils.toBean(businessProducts, CrmBusinessRespVO.Product.class, businessProductVO -> + MapUtils.findAndThen(productMap, businessProductVO.getProductId(), + product -> businessProductVO.setProductName(product.getName()) + .setProductNo(product.getNo()).setProductUnit(product.getUnit())))); + return businessVO; } @GetMapping("/simple-all-list") @@ -105,7 +134,8 @@ public class CrmBusinessController { reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 PageResult pageResult = businessService.getBusinessPage(reqVO, getLoginUserId()); return success(convertList(pageResult.getList(), business -> // 只返回 id、name 字段 - new CrmBusinessRespVO().setId(business.getId()).setName(business.getName()))); + new CrmBusinessRespVO().setId(business.getId()).setName(business.getName()) + .setCustomerId(business.getCustomerId()))); } @GetMapping("/page") @@ -113,7 +143,7 @@ public class CrmBusinessController { @PreAuthorize("@ss.hasPermission('crm:business:query')") public CommonResult> getBusinessPage(@Valid CrmBusinessPageReqVO pageVO) { PageResult pageResult = businessService.getBusinessPage(pageVO, getLoginUserId()); - return success(buildBusinessDetailPageResult(pageResult)); + return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/page-by-customer") @@ -123,7 +153,7 @@ public class CrmBusinessController { throw exception(CUSTOMER_NOT_EXISTS); } PageResult pageResult = businessService.getBusinessPageByCustomerId(pageReqVO); - return success(buildBusinessDetailPageResult(pageResult)); + return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/page-by-contact") @@ -131,7 +161,7 @@ public class CrmBusinessController { @PreAuthorize("@ss.hasPermission('crm:business:query')") public CommonResult> getBusinessContactPage(@Valid CrmBusinessPageReqVO pageReqVO) { PageResult pageResult = businessService.getBusinessPageByContact(pageReqVO); - return success(buildBusinessDetailPageResult(pageResult)); + return success(new PageResult<>(buildBusinessDetailList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/export-excel") @@ -141,29 +171,44 @@ public class CrmBusinessController { public void exportBusinessExcel(@Valid CrmBusinessPageReqVO exportReqVO, HttpServletResponse response) throws IOException { exportReqVO.setPageSize(PAGE_SIZE_NONE); - PageResult pageResult = businessService.getBusinessPage(exportReqVO, getLoginUserId()); + List list = businessService.getBusinessPage(exportReqVO, getLoginUserId()).getList(); // 导出 Excel ExcelUtils.write(response, "商机.xls", "数据", CrmBusinessRespVO.class, - buildBusinessDetailPageResult(pageResult).getList()); + buildBusinessDetailList(list)); } - /** - * 构建详细的商机分页结果 - * - * @param pageResult 简单的商机分页结果 - * @return 详细的商机分页结果 - */ - private PageResult buildBusinessDetailPageResult(PageResult pageResult) { - if (CollUtil.isEmpty(pageResult.getList())) { - return PageResult.empty(pageResult.getTotal()); + private List buildBusinessDetailList(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); } - List statusTypeList = businessStatusTypeService.getBusinessStatusTypeList( - convertSet(pageResult.getList(), CrmBusinessDO::getStatusTypeId)); - List statusList = businessStatusService.getBusinessStatusList( - convertSet(pageResult.getList(), CrmBusinessDO::getStatusId)); - List customerList = customerService.getCustomerList( - convertSet(pageResult.getList(), CrmBusinessDO::getCustomerId)); - return CrmBusinessConvert.INSTANCE.convertPage(pageResult, customerList, statusTypeList, statusList); + // 1.1 获取客户列表 + Map customerMap = customerService.getCustomerMap( + convertSet(list, CrmBusinessDO::getCustomerId)); + // 1.2 获取创建人、负责人列表 + Map userMap = adminUserApi.getUserMap(convertListByFlatMap(list, + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + // 1.3 获得商机状态组 + Map statusTypeMap = businessStatusTypeService.getBusinessStatusTypeMap( + convertSet(list, CrmBusinessDO::getStatusTypeId)); + Map statusMap = businessStatusService.getBusinessStatusMap( + convertSet(list, CrmBusinessDO::getStatusId)); + // 2. 拼接数据 + return BeanUtils.toBean(list, CrmBusinessRespVO.class, businessVO -> { + // 2.1 设置客户名称 + MapUtils.findAndThen(customerMap, businessVO.getCustomerId(), customer -> businessVO.setCustomerName(customer.getName())); + // 2.2 设置创建人、负责人名称 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(businessVO.getCreator()), + user -> businessVO.setCreatorName(user.getNickname())); + MapUtils.findAndThen(userMap, businessVO.getOwnerUserId(), user -> { + businessVO.setOwnerUserName(user.getNickname()); + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> businessVO.setOwnerUserDeptName(dept.getName())); + }); + // 2.3 设置商机状态 + MapUtils.findAndThen(statusTypeMap, businessVO.getStatusTypeId(), statusType -> businessVO.setStatusTypeName(statusType.getName())); + MapUtils.findAndThen(statusMap, businessVO.getStatusId(), status -> businessVO.setStatusName( + businessService.getBusinessStatusName(businessVO.getEndStatus(), status))); + }); } @PutMapping("/transfer") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusController.java new file mode 100644 index 000000000..0663d1f28 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusController.java @@ -0,0 +1,126 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; + +@Tag(name = "管理后台 - CRM 商机状态") +@RestController +@RequestMapping("/crm/business-status") +@Validated +public class CrmBusinessStatusController { + + @Resource + private CrmBusinessStatusService businessStatusTypeService; + + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; + + @PostMapping("/create") + @Operation(summary = "创建商机状态") + @PreAuthorize("@ss.hasPermission('crm:business-status:create')") + public CommonResult createBusinessStatus(@Valid @RequestBody CrmBusinessStatusSaveReqVO createReqVO) { + return success(businessStatusTypeService.createBusinessStatus(createReqVO)); + } + + @PutMapping("/update") + @Operation(summary = "更新商机状态") + @PreAuthorize("@ss.hasPermission('crm:business-status:update')") + public CommonResult updateBusinessStatus(@Valid @RequestBody CrmBusinessStatusSaveReqVO updateReqVO) { + businessStatusTypeService.updateBusinessStatus(updateReqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除商机状态") + @Parameter(name = "id", description = "编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:business-status:delete')") + public CommonResult deleteBusinessStatusType(@RequestParam("id") Long id) { + businessStatusTypeService.deleteBusinessStatusType(id); + return success(true); + } + + @GetMapping("/get") + @Operation(summary = "获得商机状态") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('crm:business-status:query')") + public CommonResult getBusinessStatusType(@RequestParam("id") Long id) { + CrmBusinessStatusTypeDO statusType = businessStatusTypeService.getBusinessStatusType(id); + if (statusType == null) { + return success(null); + } + List statuses = businessStatusTypeService.getBusinessStatusListByTypeId(id); + return success(BeanUtils.toBean(statusType, CrmBusinessStatusRespVO.class, + statusTypeVO -> statusTypeVO.setStatuses(BeanUtils.toBean(statuses, CrmBusinessStatusRespVO.Status.class)))); + } + + @GetMapping("/page") + @Operation(summary = "获得商机状态分页") + @PreAuthorize("@ss.hasPermission('crm:business-status:query')") + public CommonResult> getBusinessStatusPage(@Valid PageParam pageReqVO) { + // 1. 查询数据 + PageResult pageResult = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(PageResult.empty(pageResult.getTotal())); + } + // 2. 拼接数据 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), statusType -> Long.parseLong(statusType.getCreator()))); + Map deptMap = deptApi.getDeptMap( + convertSetByFlatMap(pageResult.getList(), CrmBusinessStatusTypeDO::getDeptIds, Collection::stream)); + return success(BeanUtils.toBean(pageResult, CrmBusinessStatusRespVO.class, statusTypeVO -> { + statusTypeVO.setCreator(userMap.get(NumberUtils.parseLong(statusTypeVO.getCreator())).getNickname()); + statusTypeVO.setDeptNames(convertList(statusTypeVO.getDeptIds(), + deptId -> deptMap.containsKey(deptId) ? deptMap.get(deptId).getName() : null)); + })); + } + + @GetMapping("/type-simple-list") + @Operation(summary = "获得商机状态组列表") + public CommonResult> getBusinessStatusTypeSimpleList() { + List list = businessStatusTypeService.getBusinessStatusTypeList(); + // 过滤掉部门不匹配的 + Long deptId = adminUserApi.getUser(getLoginUserId()).getDeptId(); + list.removeIf(statusType -> CollUtil.isNotEmpty(statusType.getDeptIds()) && !statusType.getDeptIds().contains(deptId)); + return success(BeanUtils.toBean(list, CrmBusinessStatusRespVO.class)); + } + + @GetMapping("/status-simple-list") + @Operation(summary = "获得商机状态列表") + @Parameter(name = "typeId", description = "商机状态组", required = true, example = "1024") + public CommonResult> getBusinessStatusSimpleList(@RequestParam("typeId") Long typeId) { + List list = businessStatusTypeService.getBusinessStatusListByTypeId(typeId); + return success(BeanUtils.toBean(list, CrmBusinessStatusRespVO.Status.class)); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java deleted file mode 100644 index 337590044..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessStatusTypeController.java +++ /dev/null @@ -1,141 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.business; - -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; -import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO; -import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusConvert; -import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessStatusTypeConvert; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusService; -import cn.iocoder.yudao.module.crm.service.business.CrmBusinessStatusTypeService; -import cn.iocoder.yudao.module.system.api.dept.DeptApi; -import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.annotation.Resource; -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; - -import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.Set; - -import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; - -@Tag(name = "管理后台 - CRM 商机状态类型") -@RestController -@RequestMapping("/crm/business-status-type") -@Validated -public class CrmBusinessStatusTypeController { - - @Resource - private CrmBusinessStatusTypeService businessStatusTypeService; - - @Resource - private CrmBusinessStatusService businessStatusService; - - @Resource - private DeptApi deptApi; - - @PostMapping("/create") - @Operation(summary = "创建商机状态类型") - @PreAuthorize("@ss.hasPermission('crm:business-status-type:create')") - public CommonResult createBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeSaveReqVO createReqVO) { - return success(businessStatusTypeService.createBusinessStatusType(createReqVO)); - } - - @PutMapping("/update") - @Operation(summary = "更新商机状态类型") - @PreAuthorize("@ss.hasPermission('crm:business-status-type:update')") - public CommonResult updateBusinessStatusType(@Valid @RequestBody CrmBusinessStatusTypeSaveReqVO updateReqVO) { - businessStatusTypeService.updateBusinessStatusType(updateReqVO); - return success(true); - } - - @DeleteMapping("/delete") - @Operation(summary = "删除商机状态类型") - @Parameter(name = "id", description = "编号", required = true) - @PreAuthorize("@ss.hasPermission('crm:business-status-type:delete')") - public CommonResult deleteBusinessStatusType(@RequestParam("id") Long id) { - businessStatusTypeService.deleteBusinessStatusType(id); - return success(true); - } - - @GetMapping("/get") - @Operation(summary = "获得商机状态类型") - @Parameter(name = "id", description = "编号", required = true, example = "1024") - @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')") - public CommonResult getBusinessStatusType(@RequestParam("id") Long id) { - CrmBusinessStatusTypeDO statusType = businessStatusTypeService.getBusinessStatusType(id); - // 处理状态回显 - // TODO @lzxhqs:可以在 businessStatusService 加个 getBusinessStatusListByTypeId 方法,直接返回 List 哈,常用的,尽量封装个简单易懂的方法,不用追求绝对通用哈; - CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO(); - queryVO.setTypeId(id); - List statusList = businessStatusService.selectList(queryVO); - return success(CrmBusinessStatusTypeConvert.INSTANCE.convert(statusType, statusList)); - } - - @GetMapping("/page") - @Operation(summary = "获得商机状态类型分页") - @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')") - public CommonResult> getBusinessStatusTypePage(@Valid CrmBusinessStatusTypePageReqVO pageReqVO) { - PageResult pageResult = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO); - // 处理部门回显 - Set deptIds = CollectionUtils.convertSetByFlatMap(pageResult.getList(), CrmBusinessStatusTypeDO::getDeptIds,Collection::stream); - List deptList = deptApi.getDeptList(deptIds); - return success(CrmBusinessStatusTypeConvert.INSTANCE.convertPage(pageResult, deptList)); - } - - @GetMapping("/export-excel") - @Operation(summary = "导出商机状态类型 Excel") - @PreAuthorize("@ss.hasPermission('crm:business-status-type:export')") - @OperateLog(type = EXPORT) - public void exportBusinessStatusTypeExcel(@Valid CrmBusinessStatusTypePageReqVO pageReqVO, - HttpServletResponse response) throws IOException { - pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); - List list = businessStatusTypeService.getBusinessStatusTypePage(pageReqVO).getList(); - // 导出 Excel - ExcelUtils.write(response, "商机状态类型.xls", "数据", CrmBusinessStatusTypeRespVO.class, - BeanUtils.toBean(list, CrmBusinessStatusTypeRespVO.class)); - } - - @GetMapping("/get-simple-list") - @Operation(summary = "获得商机状态类型列表") - @PreAuthorize("@ss.hasPermission('crm:business-status-type:query')") - public CommonResult> getBusinessStatusTypeList() { - CrmBusinessStatusTypeQueryVO queryVO = new CrmBusinessStatusTypeQueryVO(); - queryVO.setStatus(CommonStatusEnum.ENABLE.getStatus()); - List list = businessStatusTypeService.selectList(queryVO); - return success(BeanUtils.toBean(list, CrmBusinessStatusTypeRespVO.class)); - } - - // TODO @ljlleo 这个接口,是不是可以和 getBusinessStatusTypeList 合并成一个? - @GetMapping("/get-status-list") - @Operation(summary = "获得商机状态列表") - @PreAuthorize("@ss.hasPermission('crm:business-status:query')") - public CommonResult> getBusinessStatusListByTypeId(@RequestParam("typeId") Long typeId) { - CrmBusinessStatusQueryVO queryVO = new CrmBusinessStatusQueryVO(); - queryVO.setTypeId(typeId); - List list = businessStatusService.selectList(queryVO); - return success(CrmBusinessStatusConvert.INSTANCE.convertList(list)); - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessExcelVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessExcelVO.java deleted file mode 100644 index a11949ecd..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessExcelVO.java +++ /dev/null @@ -1,75 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; - -import com.alibaba.excel.annotation.ExcelProperty; -import lombok.Data; - -import java.math.BigDecimal; -import java.time.LocalDateTime; -import java.util.Set; - -/** - * 商机 Excel VO - * - * @author ljlleo - */ -@Data -public class CrmBusinessExcelVO { - - @ExcelProperty("主键") - private Long id; - - @ExcelProperty("商机名称") - private String name; - - @ExcelProperty("商机状态类型编号") - private Long statusTypeId; - - @ExcelProperty("商机状态编号") - private Long statusId; - - @ExcelProperty("下次联系时间") - private LocalDateTime contactNextTime; - - @ExcelProperty("客户编号") - private Long customerId; - - @ExcelProperty("预计成交日期") - private LocalDateTime dealTime; - - @ExcelProperty("商机金额") - private BigDecimal price; - - @ExcelProperty("整单折扣") - private BigDecimal discountPercent; - - @ExcelProperty("产品总金额") - private BigDecimal productPrice; - - @ExcelProperty("备注") - private String remark; - - @ExcelProperty("负责人的用户编号") - private Long ownerUserId; - - @ExcelProperty("创建时间") - private LocalDateTime createTime; - - @ExcelProperty("只读权限的用户编号数组") - private Set roUserIds; - - @ExcelProperty("读写权限的用户编号数组") - private Set rwUserIds; - - @ExcelProperty("1赢单2输单3无效") - private Integer endStatus; - - @ExcelProperty("结束时的备注") - private String endRemark; - - @ExcelProperty("最后跟进时间") - private LocalDateTime contactLastTime; - - @ExcelProperty("跟进状态") - private Integer followUpStatus; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java index d3b6ab2fb..49cdcb80b 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java @@ -1,69 +1,144 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; +import lombok.NoArgsConstructor; import java.math.BigDecimal; import java.time.LocalDateTime; +import java.util.List; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 商机 Response VO") +@Schema(description = "管理后台 - CRM 商机 Response VO") @Data +@ExcelIgnoreUnannotated public class CrmBusinessRespVO { - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129") + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129") + @ExcelProperty("编号") private Long id; @Schema(description = "商机名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") - @NotNull(message = "商机名称不能为空") + @ExcelProperty("商机名称") private String name; - @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") - @NotNull(message = "商机状态类型不能为空") - private Long statusTypeId; - - @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") - @NotNull(message = "商机状态不能为空") - private Long statusId; - - @Schema(description = "下次联系时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime contactNextTime; - @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299") - @NotNull(message = "客户不能为空") private Long customerId; - - @Schema(description = "预计成交日期") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime dealTime; - - @Schema(description = "商机金额", example = "12371") - private Integer price; - - // TODO @ljileo:折扣使用 Integer 类型,存储时,默认 * 100;展示的时候,前端需要 / 100;避免精度丢失问题 - @Schema(description = "整单折扣") - private Integer discountPercent; - - @Schema(description = "产品总金额", example = "12025") - private BigDecimal productPrice; - - @Schema(description = "备注", example = "随便") - private String remark; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; - @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @ExcelProperty("客户名称") private String customerName; - @Schema(description = "状态类型名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中") + @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example ="true") + @ExcelProperty("跟进状态") + private Boolean followUpStatus; + + @Schema(description = "最后跟进时间") + @ExcelProperty("最后跟进时间") + private LocalDateTime contactLastTime; + + @Schema(description = "下次联系时间") + @ExcelProperty("下次联系时间") + private LocalDateTime contactNextTime; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("负责人的用户编号") + private Long ownerUserId; + @Schema(description = "负责人名字", example = "25682") + @ExcelProperty("负责人名字") + private String ownerUserName; + @Schema(description = "负责人部门") + @ExcelProperty("负责人部门") + private String ownerUserDeptName; + + @Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") + private Long statusTypeId; + @Schema(description = "商机状组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "进行中") + @ExcelProperty("商机状态组") private String statusTypeName; + @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") + private Long statusId; @Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "跟进中") + @ExcelProperty("商机状态") private String statusName; + @Schema + @ExcelProperty("结束状态") + private Integer endStatus; + + @ExcelProperty("结束时的备注") + private String endRemark; + + @Schema(description = "预计成交日期") + @ExcelProperty("预计成交日期") + private LocalDateTime dealTime; + + @Schema(description = "产品总金额", example = "12025") + @ExcelProperty("产品总金额") + private BigDecimal totalProductPrice; + + @Schema(description = "整单折扣") + @ExcelProperty("整单折扣") + private BigDecimal discountPercent; + + @Schema(description = "商机总金额", example = "12371") + @ExcelProperty("商机总金额") + private BigDecimal totalPrice; + + @Schema(description = "备注", example = "随便") + @ExcelProperty("备注") + private String remark; + + @Schema(description = "创建人", example = "1024") + @ExcelProperty("创建人") + private String creator; + @Schema(description = "创建人名字", example = "芋道源码") + @ExcelProperty("创建人名字") + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("更新时间") + private LocalDateTime updateTime; + + @Schema(description = "产品列表") + private List products; + + @Schema(description = "产品列表") + @Data + @NoArgsConstructor + @AllArgsConstructor + public static class Product { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") + private Long id; + + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") + private Long productId; + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") + private String productNo; + @Schema(description = "产品单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + private Integer productUnit; + + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") + private BigDecimal productPrice; + + @Schema(description = "商机价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") + private BigDecimal businessPrice; + + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") + private BigDecimal count; + + @Schema(description = "总计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") + private BigDecimal totalPrice; + + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java index 0be6264eb..a9ebaae05 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java @@ -1,8 +1,7 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus; import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFunction; import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; @@ -29,75 +28,68 @@ public class CrmBusinessSaveReqVO { @NotNull(message = "商机名称不能为空") private String name; - @Schema(description = "商机状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") - @DiffLogField(name = "商机状态") - @NotNull(message = "商机状态类型不能为空") - private Long statusTypeId; - - @Schema(description = "商机状态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "30320") - @DiffLogField(name = "商机状态") - @NotNull(message = "商机状态不能为空") - private Long statusId; + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299") + @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) + @NotNull(message = "客户不能为空") + private Long customerId; @Schema(description = "下次联系时间") @DiffLogField(name = "下次联系时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime contactNextTime; - @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "10299") - @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) - @NotNull(message = "客户不能为空") - private Long customerId; + @Schema(description = "负责人用户编号", example = "14334") + @NotNull(message = "负责人不能为空") + @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) + private Long ownerUserId; + + @Schema(description = "商机状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "25714") + @DiffLogField(name = "商机状态组") + @NotNull(message = "商机状态组不能为空") + private Long statusTypeId; @Schema(description = "预计成交日期") @DiffLogField(name = "预计成交日期") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime dealTime; - @Schema(description = "商机金额", example = "12371") - @DiffLogField(name = "商机金额") - private Integer price; - - @Schema(description = "整单折扣") + @Schema(description = "整单折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "55.00") @DiffLogField(name = "整单折扣") - private Integer discountPercent; - - @Schema(description = "产品总金额", example = "12025") - @DiffLogField(name = "产品总金额") - private BigDecimal productPrice; + @NotNull(message = "整单折扣不能为空") + private BigDecimal discountPercent; @Schema(description = "备注", example = "随便") @DiffLogField(name = "备注") private String remark; - @Schema(description = "结束状态", example = "1") - @InEnum(CrmBizEndStatus.class) - private Integer endStatus; - @Schema(description = "联系人编号", example = "110") private Long contactId; // 使用场景,在【联系人详情】添加商机时,如果需要关联两者,需要传递 contactId 字段 - // TODO @puhui999:传递 items 就行啦; @Schema(description = "产品列表") - private List productItems; + private List products; @Schema(description = "产品列表") @Data @NoArgsConstructor @AllArgsConstructor - public static class CrmBusinessProductItem { + public static class Product { @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") @NotNull(message = "产品编号不能为空") - private Long id; + private Long productId; + + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") + @NotNull(message = "产品单价不能为空") + private BigDecimal productPrice; + + @Schema(description = "商机价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") + @NotNull(message = "商机价格不能为空") + private BigDecimal businessPrice; @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") @NotNull(message = "产品数量不能为空") private Integer count; - @Schema(description = "产品折扣") - private Integer discountPercent; - } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessUpdateStatusReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessUpdateStatusReqVO.java new file mode 100644 index 000000000..7bfd1df93 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessUpdateStatusReqVO.java @@ -0,0 +1,30 @@ +package cn.iocoder.yudao.module.crm.controller.admin.business.vo.business; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.business.CrmBusinessEndStatusEnum; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - CRM 商机更新状态 Request VO") +@Data +public class CrmBusinessUpdateStatusReqVO { + + @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32129") + @NotNull(message = "商机编号不能为空") + private Long id; + + @Schema(description = "状态编号", example = "1") + private Long statusId; + + @Schema(description = "结束状态", example = "1") + @InEnum(value = CrmBusinessEndStatusEnum.class) + private Integer endStatus; + + @AssertTrue(message = "变更状态不正确") + public boolean isStatusValid() { + return statusId != null || endStatus != null; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusPageReqVO.java deleted file mode 100644 index b91a954e0..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusPageReqVO.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -@Schema(description = "管理后台 - 商机状态分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmBusinessStatusPageReqVO extends PageParam { - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusQueryVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusQueryVO.java deleted file mode 100644 index fbf4d06e1..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusQueryVO.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.ToString; - -import java.util.Collection; - -@Schema(description = "管理后台 - 商机状态 Query VO") -@Data -@ToString(callSuper = true) -public class CrmBusinessStatusQueryVO { - - @Schema(description = "主键集合") - private Collection idList; - - @Schema(description = "状态类型编号") - private Long typeId; -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusRespVO.java index 405a832a5..a2ee1dfe5 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusRespVO.java @@ -1,33 +1,51 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status; -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; -import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + @Schema(description = "管理后台 - 商机状态 Response VO") @Data -@ExcelIgnoreUnannotated public class CrmBusinessStatusRespVO { - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "23899") - @ExcelProperty("主键") + @Schema(description = "状态组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934") private Long id; - @Schema(description = "状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7139") - @ExcelProperty("状态类型编号") - private Long typeId; - - @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") - @ExcelProperty("状态名") + @Schema(description = "状态组名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") private String name; - @Schema(description = "赢单率") - @ExcelProperty("赢单率") - private String percent; + @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED) + private List deptIds; + @Schema(description = "使用的部门名称", requiredMode = Schema.RequiredMode.REQUIRED) + private List deptNames; - @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @ExcelProperty("排序") - private Integer sort; + @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED) + private String creator; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "状态集合", requiredMode = Schema.RequiredMode.REQUIRED) + private List statuses; + + @Data + public static class Status { + + @Schema(description = "状态编号", example = "23899") + private Long id; + + @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + private String name; + + @Schema(description = "赢单率", requiredMode = Schema.RequiredMode.REQUIRED, example = "50") + private BigDecimal percent; + + @Schema(description = "排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer sort; + + } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java index 3327b09f7..e93305c6a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/status/CrmBusinessStatusSaveReqVO.java @@ -1,32 +1,50 @@ package cn.iocoder.yudao.module.crm.controller.admin.business.vo.status; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - +import jakarta.validation.Valid; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import lombok.Data; -@Schema(description = "管理后台 - 商机状态新增/修改 Request VO") +import java.math.BigDecimal; +import java.util.List; + +@Schema(description = "管理后台 - 商机状态组新增/修改 Request VO") @Data public class CrmBusinessStatusSaveReqVO { - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "23899") + @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934") private Long id; - @Schema(description = "状态类型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7139") - @NotNull(message = "状态类型编号不能为空") - private Long typeId; - - @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") - @NotEmpty(message = "状态名不能为空") + @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + @NotEmpty(message = "状态类型名不能为空") private String name; - // TODO @lzxhqs::percent 应该是 Integer; - @Schema(description = "赢单率") - private String percent; + @Schema(description = "使用的部门编号") + private List deptIds; - // TODO @lzxhqs:这个是不是不用前端新增和修改的时候传递,交给顺序计算出来,存储起来就好了; - @Schema(description = "排序") - private Integer sort; + @Schema(description = "商机状态集合", requiredMode = Schema.RequiredMode.REQUIRED) + @NotEmpty(message = "商机状态集合不能为空") + @Valid + private List statuses; + + @Data + public static class Status { + + @Schema(description = "状态编号", example = "23899") + private Long id; + + @Schema(description = "状态名", requiredMode = Schema.RequiredMode.REQUIRED, example = "王五") + @NotEmpty(message = "状态名不能为空") + private String name; + + @Schema(description = "赢单率", requiredMode = Schema.RequiredMode.REQUIRED, example = "50") + @NotNull(message = "赢单率不能为空") + private BigDecimal percent; + + @Schema(description = "排序", hidden = true, example = "1") + private Integer sort; + + } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypePageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypePageReqVO.java deleted file mode 100644 index 03b113cc7..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypePageReqVO.java +++ /dev/null @@ -1,15 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; - -@Schema(description = "管理后台 - 商机状态类型分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmBusinessStatusTypePageReqVO extends PageParam { - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeQueryVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeQueryVO.java deleted file mode 100644 index 9c78f1afc..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeQueryVO.java +++ /dev/null @@ -1,19 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.ToString; - -import java.util.Collection; - -@Schema(description = "管理后台 - 商机状态类型 Query VO") -@Data -@ToString(callSuper = true) -public class CrmBusinessStatusTypeQueryVO { - - @Schema(description = "主键集合") - private Collection idList; - - @Schema(description = "状态") - private Integer status; -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeRespVO.java deleted file mode 100644 index 9d13d5dc3..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeRespVO.java +++ /dev/null @@ -1,44 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type; - -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; -import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; -import com.alibaba.excel.annotation.ExcelProperty; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import java.time.LocalDateTime; -import java.util.List; - -@Schema(description = "管理后台 - 商机状态类型 Response VO") -@Data -@ExcelIgnoreUnannotated -public class CrmBusinessStatusTypeRespVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934") - @ExcelProperty("主键") - private Long id; - - @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") - @ExcelProperty("状态类型名") - private String name; - - @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("使用的部门编号") - private List deptIds; - @Schema(description = "使用的部门名称", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("使用的部门名称") - private List deptNames; - - @Schema(description = "创建人", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("创建人") - private String creator; - - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("创建时间") - private LocalDateTime createTime; - - // TODO @ljlleo 字段后缀改成 statuses,保持和 deptIds 风格一致;CrmBusinessStatusDO 改成 VO 哈;一般不使用 do 直接返回 - @Schema(description = "状态集合", requiredMode = Schema.RequiredMode.REQUIRED) - private List statusList; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java deleted file mode 100644 index 23dc7742d..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/type/CrmBusinessStatusTypeSaveReqVO.java +++ /dev/null @@ -1,29 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.business.vo.type; - -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO; -import com.google.common.collect.Lists; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -import jakarta.validation.constraints.NotEmpty; -import java.util.List; - -@Schema(description = "管理后台 - 商机状态类型新增/修改 Request VO") -@Data -public class CrmBusinessStatusTypeSaveReqVO { - - @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "2934") - private Long id; - - @Schema(description = "状态类型名", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") - @NotEmpty(message = "状态类型名不能为空") - private String name; - - // TODO @lzxhqs: VO 里面,我们不使用默认值哈。这里 Lists.newArrayList() 看看怎么去掉。上面 deptIds 也是类似噢 - @Schema(description = "使用的部门编号", requiredMode = Schema.RequiredMode.REQUIRED) - private List deptIds = Lists.newArrayList(); - - @Schema(description = "商机状态集合", requiredMode = Schema.RequiredMode.REQUIRED) - private List statusList; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java index 8be62ae26..ecfef7d01 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java @@ -1,13 +1,26 @@ package cn.iocoder.yudao.module.crm.controller.admin.clue; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.*; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.service.clue.CrmClueService; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -19,12 +32,18 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static java.util.Collections.singletonList; @Tag(name = "管理后台 - 线索") @RestController @@ -34,12 +53,19 @@ public class CrmClueController { @Resource private CrmClueService clueService; + @Resource + private CrmCustomerService customerService; + + @Resource + private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; @PostMapping("/create") @Operation(summary = "创建线索") @PreAuthorize("@ss.hasPermission('crm:clue:create')") public CommonResult createClue(@Valid @RequestBody CrmClueSaveReqVO createReqVO) { - return success(clueService.createClue(createReqVO, getLoginUserId())); + return success(clueService.createClue(createReqVO)); } @PutMapping("/update") @@ -65,7 +91,14 @@ public class CrmClueController { @PreAuthorize("@ss.hasPermission('crm:clue:query')") public CommonResult getClue(@RequestParam("id") Long id) { CrmClueDO clue = clueService.getClue(id); - return success(BeanUtils.toBean(clue, CrmClueRespVO.class)); + return success(buildClueDetail(clue)); + } + + private CrmClueRespVO buildClueDetail(CrmClueDO clue) { + if (clue == null) { + return null; + } + return buildClueDetailList(singletonList(clue)).get(0); } @GetMapping("/page") @@ -73,7 +106,7 @@ public class CrmClueController { @PreAuthorize("@ss.hasPermission('crm:clue:query')") public CommonResult> getCluePage(@Valid CrmCluePageReqVO pageVO) { PageResult pageResult = clueService.getCluePage(pageVO, getLoginUserId()); - return success(BeanUtils.toBean(pageResult, CrmClueRespVO.class)); + return success(new PageResult<>(buildClueDetailList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/export-excel") @@ -84,8 +117,33 @@ public class CrmClueController { pageReqVO.setPageSize(PAGE_SIZE_NONE); List list = clueService.getCluePage(pageReqVO, getLoginUserId()).getList(); // 导出 Excel - List datas = BeanUtils.toBean(list, CrmClueRespVO.class); - ExcelUtils.write(response, "线索.xls", "数据", CrmClueRespVO.class, datas); + ExcelUtils.write(response, "线索.xls", "数据", CrmClueRespVO.class, buildClueDetailList(list)); + } + + private List buildClueDetailList(List list) { + if (CollUtil.isEmpty(list)) { + return Collections.emptyList(); + } + // 1.1 获取客户列表 + Map customerMap = customerService.getCustomerMap( + convertSet(list, CrmClueDO::getCustomerId)); + // 1.2 获取创建人、负责人列表 + Map userMap = adminUserApi.getUserMap(convertListByFlatMap(list, + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + // 2. 转换成 VO + return BeanUtils.toBean(list, CrmClueRespVO.class, clueVO -> { + clueVO.setAreaName(AreaUtils.format(clueVO.getAreaId())); + // 2.1 设置客户名称 + MapUtils.findAndThen(customerMap, clueVO.getCustomerId(), customer -> clueVO.setCustomerName(customer.getName())); + // 2.2 设置创建人、负责人名称 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(clueVO.getCreator()), + user -> clueVO.setCreatorName(user.getNickname())); + MapUtils.findAndThen(userMap, clueVO.getOwnerUserId(), user -> { + clueVO.setOwnerUserName(user.getNickname()); + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> clueVO.setOwnerUserDeptName(dept.getName())); + }); + }); } @PutMapping("/transfer") @@ -96,12 +154,20 @@ public class CrmClueController { return success(true); } - @PostMapping("/transform") + @PutMapping("/transform") @Operation(summary = "线索转化为客户") + @Parameter(name = "id", description = "编号", required = true) @PreAuthorize("@ss.hasPermission('crm:clue:update')") - public CommonResult translateCustomer(@Valid @RequestBody CrmClueTranslateReqVO reqVO) { - clueService.translateCustomer(reqVO, getLoginUserId()); + public CommonResult transformClue(@RequestParam("id") Long id) { + clueService.transformClue(id, getLoginUserId()); return success(Boolean.TRUE); } + @GetMapping("/follow-count") + @Operation(summary = "获得分配给我的、待跟进的线索数量") + @PreAuthorize("@ss.hasPermission('crm:clue:query')") + public CommonResult getFollowClueCount() { + return success(clueService.getFollowClueCount(getLoginUserId())); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java index 3ba823d54..a63d946e9 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmCluePageReqVO.java @@ -17,6 +17,9 @@ public class CrmCluePageReqVO extends PageParam { @Schema(description = "线索名称", example = "线索xxx") private String name; + @Schema(description = "转化状态", example = "2048") + private Boolean transformStatus; + @Schema(description = "电话", example = "18000000000") private String telephone; @@ -39,4 +42,7 @@ public class CrmCluePageReqVO extends PageParam { @Schema(description = "客户来源", example = "1") private Integer source; + @Schema(description = "跟进状态", example = "true") + private Boolean followUpStatus; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java index 35d30956e..56e5c2561 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueRespVO.java @@ -8,12 +8,9 @@ import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - @Schema(description = "管理后台 - 线索 Response VO") @Data @ToString(callSuper = true) @@ -24,58 +21,76 @@ public class CrmClueRespVO { @ExcelProperty("编号") private Long id; - @Schema(description = "转化状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") - @ExcelProperty(value = "转化状态", converter = DictConvert.class) - @DictFormat(DictTypeConstants.BOOLEAN_STRING) - private Boolean transformStatus; + @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx") + @ExcelProperty("线索名称") + private String name; @Schema(description = "跟进状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") @ExcelProperty(value = "跟进状态", converter = DictConvert.class) @DictFormat(DictTypeConstants.BOOLEAN_STRING) private Boolean followUpStatus; - @Schema(description = "线索名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "线索xxx") - @ExcelProperty("线索名称") - private String name; + @Schema(description = "最后跟进时间") + @ExcelProperty("最后跟进时间") + private LocalDateTime contactLastTime; - @Schema(description = "客户 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "520") - // TODO 这里需要导出成客户名称 - @ExcelProperty("客户id") - private Long customerId; + @Schema(description = "最后跟进内容", example = "吃饭、睡觉、打逗逗") + @ExcelProperty("最后跟进内容") + private String contactLastContent; @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) @ExcelProperty("下次联系时间") private LocalDateTime contactNextTime; - @Schema(description = "电话", example = "18000000000") - @ExcelProperty("电话") - private String telephone; + @Schema(description = "负责人编号") + private Long ownerUserId; + @Schema(description = "负责人名字", example = "25682") + @ExcelProperty("负责人名字") + private String ownerUserName; + @Schema(description = "负责人部门") + @ExcelProperty("负责人部门") + private String ownerUserDeptName; + + @Schema(description = "转化状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true") + @ExcelProperty(value = "转化状态", converter = DictConvert.class) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) + private Boolean transformStatus; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "520") + private Long customerId; + @Schema(description = "客户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "客户名称") + @ExcelProperty("客户名称") + private String customerName; @Schema(description = "手机号", example = "18000000000") @ExcelProperty("手机号") private String mobile; - @Schema(description = "地址", example = "北京市海淀区") - @ExcelProperty("地址") - private String address; + @Schema(description = "电话", example = "18000000000") + @ExcelProperty("电话") + private String telephone; - @Schema(description = "负责人编号") - @ExcelProperty("负责人的用户编号") - private Long ownerUserId; + @Schema(description = "QQ", example = "25682") + @ExcelProperty("QQ") + private String qq; - @Schema(description = "最后跟进时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @ExcelProperty("最后跟进时间") - private LocalDateTime contactLastTime; + @Schema(description = "wechat", example = "25682") + @ExcelProperty("wechat") + private String wechat; - @Schema(description = "备注", example = "随便") - @ExcelProperty("备注") - private String remark; + @Schema(description = "email", example = "25682") + @ExcelProperty("email") + private String email; - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - @ExcelProperty("创建时间") - private LocalDateTime createTime; + @Schema(description = "地区编号", example = "1024") + @ExcelProperty("地区编号") + private Integer areaId; + @Schema(description = "地区名称", example = "北京市") + @ExcelProperty("地区名称") + private String areaName; + @Schema(description = "详细地址", example = "北京市成华大道") + @ExcelProperty("详细地址") + private String detailAddress; @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") @ExcelProperty(value = "所属行业", converter = DictConvert.class) @@ -92,24 +107,23 @@ public class CrmClueRespVO { @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE) private Integer source; - @Schema(description = "网址", example = "25682") - @ExcelProperty("网址") - private String website; + @Schema(description = "备注", example = "随便") + @ExcelProperty("备注") + private String remark; - @Schema(description = "QQ", example = "25682") - @ExcelProperty("QQ") - private String qq; + @Schema(description = "创建人", example = "1024") + @ExcelProperty("创建人") + private String creator; + @Schema(description = "创建人名字", example = "芋道源码") + @ExcelProperty("创建人名字") + private String creatorName; - @Schema(description = "wechat", example = "25682") - @ExcelProperty("wechat") - private String wechat; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; - @Schema(description = "email", example = "25682") - @ExcelProperty("email") - private String email; - - @Schema(description = "客户描述", example = "25682") - @ExcelProperty("客户描述") - private String description; + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("更新时间") + private LocalDateTime updateTime; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java index 4ca004a59..a27de7059 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueSaveReqVO.java @@ -8,10 +8,12 @@ import cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLevelEnum; import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerIndustryParseFunction; import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerLevelParseFunction; import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmCustomerSourceParseFunction; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAreaParseFunction; import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; @@ -21,7 +23,7 @@ import java.time.LocalDateTime; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY; -@Schema(description = "管理后台 - CRM 线索 创建/更新 Request VO") +@Schema(description = "管理后台 - CRM 线索创建/更新 Request VO") @Data public class CrmClueSaveReqVO { @@ -33,54 +35,29 @@ public class CrmClueSaveReqVO { @NotEmpty(message = "线索名称不能为空") private String name; + @Schema(description = "最后跟进时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @DiffLogField(name = "最后跟进时间") + private LocalDateTime contactLastTime; + @Schema(description = "下次联系时间", example = "2023-10-18 01:00:00") @DiffLogField(name = "下次联系时间") @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime contactNextTime; - @Schema(description = "电话", example = "18000000000") - @DiffLogField(name = "电话") - @Telephone - private String telephone; + @Schema(description = "负责人编号", example = "2048") + @NotNull(message = "负责人编号不能为空") + private Long ownerUserId; @Schema(description = "手机号", example = "18000000000") @DiffLogField(name = "手机号") @Mobile private String mobile; - @Schema(description = "地址", example = "北京市海淀区") - @DiffLogField(name = "地址") - private String address; - - @Schema(description = "最后跟进时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @DiffLogField(name = "最后跟进时间") - private LocalDateTime contactLastTime; - - @Schema(description = "负责人编号", example = "2048") - private Long ownerUserId; - - @Schema(description = "备注", example = "随便") - @DiffLogField(name = "备注") - private String remark; - - @Schema(description = "所属行业", example = "1") - @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME) - @DictFormat(CRM_CUSTOMER_INDUSTRY) - private Integer industryId; - - @Schema(description = "客户等级", example = "2") - @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME) - @InEnum(CrmCustomerLevelEnum.class) - private Integer level; - - @Schema(description = "客户来源", example = "3") - @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME) - private Integer source; - - @Schema(description = "网址", example = "https://www.baidu.com") - @DiffLogField(name = "网址") - private String website; + @Schema(description = "电话", example = "18000000000") + @DiffLogField(name = "电话") + @Telephone + private String telephone; @Schema(description = "QQ", example = "123456789") @DiffLogField(name = "QQ") @@ -98,8 +75,35 @@ public class CrmClueSaveReqVO { @Size(max = 255, message = "邮箱长度不能超过 255 个字符") private String email; + @Schema(description = "地区编号", example = "20158") + @DiffLogField(name = "地区编号", function = SysAreaParseFunction.NAME) + private Integer areaId; + + @Schema(description = "详细地址", example = "北京市海淀区") + @DiffLogField(name = "详细地址") + private String detailAddress; + + @Schema(description = "所属行业", example = "1") + @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME) + @DictFormat(CRM_CUSTOMER_INDUSTRY) + private Integer industryId; + + @Schema(description = "客户等级", example = "2") + @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME) + @InEnum(CrmCustomerLevelEnum.class) + private Integer level; + + @Schema(description = "客户来源", example = "3") + @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME) + private Integer source; + @Schema(description = "客户描述", example = "任意文字") @DiffLogField(name = "客户描述") @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符") private String description; + + @Schema(description = "备注", example = "随便") + @DiffLogField(name = "备注") + private String remark; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTranslateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTranslateReqVO.java deleted file mode 100644 index 03a4d78f1..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/vo/CrmClueTranslateReqVO.java +++ /dev/null @@ -1,17 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.clue.vo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import lombok.Data; - -import java.util.Set; - -@Schema(description = "管理后台 - 线索转化为客户 Request VO") -@Data -public class CrmClueTranslateReqVO { - - @Schema(description = "线索编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1024, 1025]") - @NotEmpty(message = "线索编号不能为空") - private Set ids; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java index 766adb02d..5c7413159 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/CrmContactController.java @@ -2,24 +2,24 @@ package cn.iocoder.yudao.module.crm.controller.admin.contact; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.NumberUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.*; -import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService; import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import com.google.common.collect.Lists; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -37,12 +37,13 @@ import java.util.List; import java.util.Map; import java.util.stream.Stream; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static java.util.Collections.singletonList; @Tag(name = "管理后台 - CRM 联系人") @RestController @@ -60,6 +61,8 @@ public class CrmContactController { @Resource private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; @PostMapping("/create") @Operation(summary = "创建联系人") @@ -92,36 +95,24 @@ public class CrmContactController { @PreAuthorize("@ss.hasPermission('crm:contact:query')") public CommonResult getContact(@RequestParam("id") Long id) { CrmContactDO contact = contactService.getContact(id); - if (contact == null) { - throw exception(ErrorCodeConstants.CONTACT_NOT_EXISTS); - } - // 1. 获取用户名 - Map userMap = adminUserApi.getUserMap(CollUtil.removeNull(Lists.newArrayList( - NumberUtil.parseLong(contact.getCreator()), contact.getOwnerUserId()))); - // 2. 获取客户信息 - List customerList = customerService.getCustomerList( - Collections.singletonList(contact.getCustomerId())); - // 3. 直属上级 - List parentContactList = contactService.getContactListByIds( - Collections.singletonList(contact.getParentId()), getLoginUserId()); - return success(CrmContactConvert.INSTANCE.convert(contact, userMap, customerList, parentContactList)); + return success(buildContactDetail(contact)); } - @GetMapping("/list-by-ids") - @Operation(summary = "获得联系人列表") - @Parameter(name = "ids", description = "编号", required = true, example = "[1024]") - @PreAuthorize("@ss.hasPermission('crm:contact:query')") - public CommonResult> getContactListByIds(@RequestParam("ids") List ids) { - return success(BeanUtils.toBean(contactService.getContactListByIds(ids, getLoginUserId()), CrmContactRespVO.class)); + private CrmContactRespVO buildContactDetail(CrmContactDO contact) { + if (contact == null) { + return null; + } + return buildContactDetailList(singletonList(contact)).get(0); } @GetMapping("/simple-all-list") @Operation(summary = "获得联系人的精简列表") @PreAuthorize("@ss.hasPermission('crm:contact:query')") public CommonResult> getSimpleContactList() { - List list = contactService.getSimpleContactList(getLoginUserId()); + List list = contactService.getContactList(getLoginUserId()); return success(convertList(list, contact -> // 只返回 id、name 字段 - new CrmContactRespVO().setId(contact.getId()).setName(contact.getName()))); + new CrmContactRespVO().setId(contact.getId()).setName(contact.getName()) + .setCustomerId(contact.getCustomerId()))); } @GetMapping("/page") @@ -129,7 +120,7 @@ public class CrmContactController { @PreAuthorize("@ss.hasPermission('crm:contact:query')") public CommonResult> getContactPage(@Valid CrmContactPageReqVO pageVO) { PageResult pageResult = contactService.getContactPage(pageVO, getLoginUserId()); - return success(buildContactDetailPage(pageResult)); + return success(new PageResult<>(buildContactDetailList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/page-by-customer") @@ -137,7 +128,15 @@ public class CrmContactController { public CommonResult> getContactPageByCustomer(@Valid CrmContactPageReqVO pageVO) { Assert.notNull(pageVO.getCustomerId(), "客户编号不能为空"); PageResult pageResult = contactService.getContactPageByCustomerId(pageVO); - return success(buildContactDetailPage(pageResult)); + return success(new PageResult<>(buildContactDetailList(pageResult.getList()), pageResult.getTotal())); + } + + @GetMapping("/page-by-business") + @Operation(summary = "获得联系人分页,基于指定商机") + public CommonResult> getContactPageByBusiness(@Valid CrmContactPageReqVO pageVO) { + Assert.notNull(pageVO.getBusinessId(), "商机编号不能为空"); + PageResult pageResult = contactService.getContactPageByBusinessId(pageVO); + return success(new PageResult<>(buildContactDetailList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/export-excel") @@ -147,32 +146,39 @@ public class CrmContactController { public void exportContactExcel(@Valid CrmContactPageReqVO exportReqVO, HttpServletResponse response) throws IOException { exportReqVO.setPageNo(PAGE_SIZE_NONE); - PageResult pageResult = contactService.getContactPage(exportReqVO, getLoginUserId()); - ExcelUtils.write(response, "联系人.xls", "数据", CrmContactRespVO.class, - buildContactDetailPage(pageResult).getList()); + List list = contactService.getContactPage(exportReqVO, getLoginUserId()).getList(); + ExcelUtils.write(response, "联系人.xls", "数据", CrmContactRespVO.class, buildContactDetailList(list)); } - /** - * 构建详细的联系人分页结果 - * - * @param pageResult 简单的联系人分页结果 - * @return 详细的联系人分页结果 - */ - private PageResult buildContactDetailPage(PageResult pageResult) { - List contactList = pageResult.getList(); + private List buildContactDetailList(List contactList) { if (CollUtil.isEmpty(contactList)) { - return PageResult.empty(pageResult.getTotal()); + return Collections.emptyList(); } - // 1. 获取客户列表 - List crmCustomerDOList = customerService.getCustomerList( + // 1.1 获取客户列表 + Map customerMap = customerService.getCustomerMap( convertSet(contactList, CrmContactDO::getCustomerId)); - // 2. 获取创建人、负责人列表 + // 1.2 获取创建人、负责人列表 Map userMap = adminUserApi.getUserMap(convertListByFlatMap(contactList, contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); - // 3. 直属上级 - List parentContactList = contactService.getContactListByIds( - convertSet(contactList, CrmContactDO::getParentId), getLoginUserId()); - return CrmContactConvert.INSTANCE.convertPage(pageResult, userMap, crmCustomerDOList, parentContactList); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + // 1.3 直属上级 Map + Map parentContactMap = contactService.getContactMap( + convertSet(contactList, CrmContactDO::getParentId)); + // 2. 转换成 VO + return BeanUtils.toBean(contactList, CrmContactRespVO.class, contactVO -> { + contactVO.setAreaName(AreaUtils.format(contactVO.getAreaId())); + // 2.1 设置客户名称 + MapUtils.findAndThen(customerMap, contactVO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName())); + // 2.2 设置创建人、负责人名称 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(contactVO.getCreator()), + user -> contactVO.setCreatorName(user.getNickname())); + MapUtils.findAndThen(userMap, contactVO.getOwnerUserId(), user -> { + contactVO.setOwnerUserName(user.getNickname()); + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> contactVO.setOwnerUserDeptName(dept.getName())); + }); + // 2.3 设置直属上级名称 + findAndThen(parentContactMap, contactVO.getParentId(), contact -> contactVO.setParentName(contact.getName())); + }); } @PutMapping("/transfer") @@ -183,7 +189,7 @@ public class CrmContactController { return success(true); } - // ================== 关联/取关联系人 =================== + // ================== 关联/取关商机 =================== @PostMapping("/create-business-list") @Operation(summary = "创建联系人与商机的关联") @@ -193,6 +199,15 @@ public class CrmContactController { return success(true); } + + @PostMapping("/create-business-list2") + @Operation(summary = "创建联系人与商机的关联") + @PreAuthorize("@ss.hasPermission('crm:contact:create-business')") + public CommonResult createContactBusinessList2(@Valid @RequestBody CrmContactBusiness2ReqVO createReqVO) { + contactBusinessLinkService.createContactBusinessList2(createReqVO); + return success(true); + } + @DeleteMapping("/delete-business-list") @Operation(summary = "删除联系人与联系人的关联") @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')") @@ -201,4 +216,12 @@ public class CrmContactController { return success(true); } + @DeleteMapping("/delete-business-list2") + @Operation(summary = "删除联系人与联系人的关联") + @PreAuthorize("@ss.hasPermission('crm:contact:delete-business')") + public CommonResult deleteContactBusinessList(@Valid @RequestBody CrmContactBusiness2ReqVO deleteReqVO) { + contactBusinessLinkService.deleteContactBusinessList2(deleteReqVO); + return success(true); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusiness2ReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusiness2ReqVO.java new file mode 100644 index 000000000..5fac57cff --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusiness2ReqVO.java @@ -0,0 +1,22 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contact.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; + +@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 【商机关联联系人】用于关联,取消关联的操作 +@Data +public class CrmContactBusiness2ReqVO { + + @Schema(description = "商机编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "7638") + @NotNull(message="商机不能为空") + private Long businessId; + + @Schema(description = "联系人编号数组", requiredMode = Schema.RequiredMode.REQUIRED, example = "20878") + @NotEmpty(message="联系人数组不能为空") + private List contactIds; + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java index 9b360f84b..7218494b4 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactBusinessReqVO.java @@ -7,7 +7,7 @@ import lombok.Data; import java.util.List; -@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 用于关联,取消关联的操作 +@Schema(description = "管理后台 - CRM 联系人商机 Request VO") // 【联系人关联商机】用于关联,取消关联的操作 @Data public class CrmContactBusinessReqVO { diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java index 75294a1bd..6698a3855 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactPageReqVO.java @@ -39,4 +39,7 @@ public class CrmContactPageReqVO extends PageParam { @InEnum(CrmSceneTypeEnum.class) private Integer sceneType; // 场景类型,为 null 时则表示全部 + @Schema(description = "商机编号", example = "10430") + private Long businessId; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java index d99ea703c..b2b1e8384 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactRespVO.java @@ -20,29 +20,36 @@ public class CrmContactRespVO { @Schema(description = "主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "3167") private Long id; - @Schema(description = "姓名", example = "芋艿") - @ExcelProperty(value = "姓名", order = 1) + @Schema(description = "联系人姓名", example = "芋艿") + @ExcelProperty(value = "联系人姓名", order = 1) private String name; @Schema(description = "客户编号", example = "10795") private Long customerId; + @ExcelProperty(value = "客户名称", order = 2) + @Schema(description = "客户名字", example = "test") + private String customerName; - @Schema(description = "性别") - @ExcelProperty(value = "性别", converter = DictConvert.class, order = 3) - @DictFormat(cn.iocoder.yudao.module.system.enums.DictTypeConstants.USER_SEX) - private Integer sex; + @Schema(description = "最后跟进时间") + @ExcelProperty(value = "最后跟进时间", order = 6) + private LocalDateTime contactLastTime; - @Schema(description = "职位") - @ExcelProperty(value = "职位", order = 3) - private String post; + @Schema(description = "最后跟进内容") + @ExcelProperty(value = "最后跟进内容", order = 6) + private String contactLastContent; - @Schema(description = "是否关键决策人") - @ExcelProperty(value = "是否关键决策人", converter = DictConvert.class, order = 3) - @DictFormat(DictTypeConstants.BOOLEAN_STRING) - private Boolean master; + @Schema(description = "下次联系时间") + @ExcelProperty(value = "下次联系时间", order = 6) + private LocalDateTime contactNextTime; - @Schema(description = "直属上级", example = "23457") - private Long parentId; + @Schema(description = "负责人编号") + private Long ownerUserId; + @Schema(description = "负责人名字", example = "25682") + @ExcelProperty("负责人名字") + private String ownerUserName; + @Schema(description = "负责人部门") + @ExcelProperty("负责人部门") + private String ownerUserDeptName; @Schema(description = "手机号", example = "1387171766") @ExcelProperty(value = "手机号", order = 4) @@ -52,6 +59,10 @@ public class CrmContactRespVO { @ExcelProperty(value = "电话", order = 4) private String telephone; + @Schema(description = "电子邮箱", example = "1111@22.com") + @ExcelProperty(value = "邮箱", order = 4) + private String email; + @Schema(description = "QQ", example = "197272662") @ExcelProperty(value = "QQ", order = 4) private Long qq; @@ -60,53 +71,52 @@ public class CrmContactRespVO { @ExcelProperty(value = "微信", order = 4) private String wechat; - @Schema(description = "电子邮箱", example = "1111@22.com") - @ExcelProperty(value = "邮箱", order = 4) - private String email; - @Schema(description = "地区编号", example = "20158") private Integer areaId; + @Schema(description = "地区名", example = "上海上海市浦东新区") + @ExcelProperty(value = "地区", order = 5) + private String areaName; @Schema(description = "地址") @ExcelProperty(value = "地址", order = 5) private String detailAddress; - @Schema(description = "备注", example = "你说的对") - @ExcelProperty(value = "备注", order = 6) - private String remark; + @Schema(description = "性别") + @ExcelProperty(value = "性别", converter = DictConvert.class, order = 3) + @DictFormat(cn.iocoder.yudao.module.system.enums.DictTypeConstants.USER_SEX) + private Integer sex; - @Schema(description = "负责人用户编号", example = "14334") - private Long ownerUserId; + @Schema(description = "是否关键决策人") + @ExcelProperty(value = "是否关键决策人", converter = DictConvert.class, order = 3) + @DictFormat(DictTypeConstants.BOOLEAN_STRING) + private Boolean master; - @Schema(description = "最后跟进时间") - @ExcelProperty(value = "最后跟进时间", order = 6) - private LocalDateTime contactLastTime; - - @Schema(description = "下次联系时间") - @ExcelProperty(value = "下次联系时间", order = 6) - private LocalDateTime contactNextTime; - - @Schema(description = "创建人", example = "25682") - private String creator; - - @Schema(description = "创建人名字", example = "test") - @ExcelProperty(value = "创建人", order = 8) - private String creatorName; - - @ExcelProperty(value = "客户名称", order = 2) - @Schema(description = "客户名字", example = "test") - private String customerName; - - @Schema(description = "负责人", example = "test") - @ExcelProperty(value = "负责人", order = 7) - private String ownerUserName; + @Schema(description = "职位") + @ExcelProperty(value = "职位", order = 3) + private String post; + @Schema(description = "直属上级", example = "23457") + private Long parentId; @Schema(description = "直属上级名", example = "芋头") @ExcelProperty(value = "直属上级", order = 4) private String parentName; - @Schema(description = "地区名", example = "上海上海市浦东新区") - @ExcelProperty(value = "地区", order = 5) - private String areaName; + @Schema(description = "备注", example = "你说的对") + @ExcelProperty(value = "备注", order = 6) + private String remark; + + @Schema(description = "创建人", example = "25682") + private String creator; + @Schema(description = "创建人名字", example = "test") + @ExcelProperty(value = "创建人", order = 8) + private String creatorName; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("更新时间") + private LocalDateTime updateTime; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java index 299b1fbbb..874716f4e 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contact/vo/CrmContactSaveReqVO.java @@ -13,7 +13,6 @@ import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; @Schema(description = "管理后台 - CRM 联系人创建/更新 Request VO") @Data @@ -28,24 +27,19 @@ public class CrmContactSaveReqVO { private String name; @Schema(description = "客户编号", example = "10795") + @NotNull(message = "客户编号不能为空") @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) private Long customerId; - @Schema(description = "性别") - @DiffLogField(name = "性别", function = SysSexParseFunction.NAME) - private Integer sex; + @Schema(description = "下次联系时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) + @DiffLogField(name = "下次联系时间") + private LocalDateTime contactNextTime; - @Schema(description = "职位") - @DiffLogField(name = "职位") - private String post; - - @Schema(description = "是否关键决策人") - @DiffLogField(name = "关键决策人", function = SysBooleanParseFunction.NAME) - private Boolean master; - - @Schema(description = "直属上级", example = "23457") - @DiffLogField(name = "直属上级", function = CrmContactParseFunction.NAME) - private Long parentId; + @Schema(description = "负责人用户编号", example = "14334") + @NotNull(message = "负责人不能为空") + @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) + private Long ownerUserId; @Schema(description = "手机号", example = "1387171766") @Mobile @@ -78,25 +72,26 @@ public class CrmContactSaveReqVO { @DiffLogField(name = "地址") private String detailAddress; + @Schema(description = "性别") + @DiffLogField(name = "性别", function = SysSexParseFunction.NAME) + private Integer sex; + + @Schema(description = "是否关键决策人") + @DiffLogField(name = "关键决策人", function = SysBooleanParseFunction.NAME) + private Boolean master; + + @Schema(description = "职位") + @DiffLogField(name = "职位") + private String post; + + @Schema(description = "直属上级", example = "23457") + @DiffLogField(name = "直属上级", function = CrmContactParseFunction.NAME) + private Long parentId; + @Schema(description = "备注", example = "你说的对") @DiffLogField(name = "备注") private String remark; - @Schema(description = "负责人用户编号", example = "14334") - @NotNull(message = "负责人不能为空") - @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) - private Long ownerUserId; - - @Schema(description = "最后跟进时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @DiffLogField(name = "最后跟进时间") - private LocalDateTime contactLastTime; - - @Schema(description = "下次联系时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY) - @DiffLogField(name = "下次联系时间") - private LocalDateTime contactNextTime; - @Schema(description = "关联商机 ID", example = "122233") private Long businessId; // 注意:该字段用于在【商机】详情界面「新建联系人」时,自动进行关联 diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractConfigController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractConfigController.java new file mode 100644 index 000000000..a97c3bfd6 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractConfigController.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contract; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config.CrmContractConfigRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config.CrmContractConfigSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO; +import cn.iocoder.yudao.module.crm.service.contract.CrmContractConfigService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - CRM 合同配置") +@RestController +@RequestMapping("/crm/contract-config") +@Validated +public class CrmContractConfigController { + + @Resource + private CrmContractConfigService contractConfigService; + + @GetMapping("/get") + @Operation(summary = "获取合同配置") + @PreAuthorize("@ss.hasPermission('crm:contract-config:query')") + public CommonResult getCustomerPoolConfig() { + CrmContractConfigDO config = contractConfigService.getContractConfig(); + return success(BeanUtils.toBean(config, CrmContractConfigRespVO.class)); + } + + @PutMapping("/save") + @Operation(summary = "更新合同配置") + @PreAuthorize("@ss.hasPermission('crm:contract-config:update')") + public CommonResult saveCustomerPoolConfig(@Valid @RequestBody CrmContractConfigSaveReqVO updateReqVO) { + contractConfigService.saveContractConfig(updateReqVO); + return success(true); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java index ace5d1817..d0f30db23 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java @@ -3,16 +3,17 @@ package cn.iocoder.yudao.module.crm.controller.admin.contract; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; -import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; @@ -24,6 +25,9 @@ import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.product.CrmProductService; +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; @@ -37,6 +41,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; +import java.math.BigDecimal; import java.util.Collections; import java.util.List; import java.util.Map; @@ -44,6 +49,7 @@ import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; import static java.util.Collections.singletonList; @@ -64,8 +70,13 @@ public class CrmContractController { private CrmBusinessService businessService; @Resource private CrmProductService productService; + @Resource + private CrmReceivableService receivableService; + @Resource private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; @PostMapping("/create") @Operation(summary = "创建合同") @@ -96,15 +107,24 @@ public class CrmContractController { @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('crm:contract:query')") public CommonResult getContract(@RequestParam("id") Long id) { - // 1. 查询合同 CrmContractDO contract = contractService.getContract(id); - if (contract == null) { - return success(null); - } + return success(buildContractDetail(contract)); + } - // 2. 拼接合同信息 - List respVOList = buildContractDetailList(singletonList(contract)); - return success(respVOList.get(0)); + private CrmContractRespVO buildContractDetail(CrmContractDO contract) { + if (contract == null) { + return null; + } + CrmContractRespVO contractVO = buildContractDetailList(singletonList(contract)).get(0); + // 拼接产品项 + List businessProducts = contractService.getContractProductListByContractId(contractVO.getId()); + Map productMap = productService.getProductMap( + convertSet(businessProducts, CrmContractProductDO::getProductId)); + contractVO.setProducts(BeanUtils.toBean(businessProducts, CrmContractRespVO.Product.class, businessProductVO -> + MapUtils.findAndThen(productMap, businessProductVO.getProductId(), + product -> businessProductVO.setProductName(product.getName()) + .setProductNo(product.getNo()).setProductUnit(product.getUnit())))); + return contractVO; } @GetMapping("/page") @@ -123,6 +143,14 @@ public class CrmContractController { return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList()))); } + @GetMapping("/page-by-business") + @Operation(summary = "获得合同分页,基于指定商机") + public CommonResult> getContractPageByBusiness(@Valid CrmContractPageReqVO pageVO) { + Assert.notNull(pageVO.getBusinessId(), "商机编号不能为空"); + PageResult pageResult = contractService.getContractPageByBusinessId(pageVO); + return success(BeanUtils.toBean(pageResult, CrmContractRespVO.class).setList(buildContractDetailList(pageResult.getList()))); + } + @GetMapping("/export-excel") @Operation(summary = "导出合同 Excel") @PreAuthorize("@ss.hasPermission('crm:contract:export')") @@ -151,37 +179,78 @@ public class CrmContractController { return success(true); } - /** - * 构建详细的合同结果 - * - * @param contractList 原始合同信息 - * @return 细的合同结果 - */ private List buildContractDetailList(List contractList) { if (CollUtil.isEmpty(contractList)) { return Collections.emptyList(); } - // 1. 获取客户列表 - List customerList = customerService.getCustomerList( + // 1.1 获取客户列表 + Map customerMap = customerService.getCustomerMap( convertSet(contractList, CrmContractDO::getCustomerId)); - // 2. 获取创建人、负责人列表 + // 1.2 获取创建人、负责人列表 Map userMap = adminUserApi.getUserMap(convertListByFlatMap(contractList, contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); - // 3. 获取联系人 - Map contactMap = convertMap(contactService.getContactListByIds(convertSet(contractList, - CrmContractDO::getContactId)), CrmContactDO::getId); - // 4. 获取商机 - Map businessMap = convertMap(businessService.getBusinessList(convertSet(contractList, - CrmContractDO::getBusinessId)), CrmBusinessDO::getId); - // 5. 获取合同关联的商品 - Map contractProductMap = null; - List productList = null; - if (contractList.size() == 1) { - List contractProductList = contractService.getContractProductListByContractId(contractList.get(0).getId()); - contractProductMap = convertMap(contractProductList, CrmContractProductDO::getProductId); - productList = productService.getProductListByIds(convertSet(contractProductList, CrmContractProductDO::getProductId)); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + // 1.3 获取联系人 + Map contactMap = convertMap(contactService.getContactList(convertSet(contractList, + CrmContractDO::getSignContactId)), CrmContactDO::getId); + // 1.4 获取商机 + Map businessMap = businessService.getBusinessMap( + convertSet(contractList, CrmContractDO::getBusinessId)); + // 1.5 获得已回款金额 + Map receivablePriceMap = receivableService.getReceivablePriceMapByContractId( + convertSet(contractList, CrmContractDO::getId)); + // 2. 拼接数据 + return BeanUtils.toBean(contractList, CrmContractRespVO.class, contractVO -> { + // 2.1 设置客户信息 + findAndThen(customerMap, contractVO.getCustomerId(), customer -> contractVO.setCustomerName(customer.getName())); + // 2.2 设置用户信息 + findAndThen(userMap, Long.parseLong(contractVO.getCreator()), user -> contractVO.setCreatorName(user.getNickname())); + MapUtils.findAndThen(userMap, contractVO.getOwnerUserId(), user -> { + contractVO.setOwnerUserName(user.getNickname()); + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> contractVO.setOwnerUserDeptName(dept.getName())); + }); + findAndThen(userMap, contractVO.getSignUserId(), user -> contractVO.setSignUserName(user.getNickname())); + // 2.3 设置联系人信息 + findAndThen(contactMap, contractVO.getSignContactId(), contact -> contractVO.setSignContactName(contact.getName())); + // 2.4 设置商机信息 + findAndThen(businessMap, contractVO.getBusinessId(), business -> contractVO.setBusinessName(business.getName())); + // 2.5 设置已回款金额 + contractVO.setTotalReceivablePrice(receivablePriceMap.getOrDefault(contractVO.getId(), BigDecimal.ZERO)); + }); + } + + @GetMapping("/audit-count") + @Operation(summary = "获得待审核合同数量") + @PreAuthorize("@ss.hasPermission('crm:contract:query')") + public CommonResult getAuditContractCount() { + return success(contractService.getAuditContractCount(getLoginUserId())); + } + + @GetMapping("/remind-count") + @Operation(summary = "获得即将到期(提醒)的合同数量") + @PreAuthorize("@ss.hasPermission('crm:contract:query')") + public CommonResult getRemindContractCount() { + return success(contractService.getRemindContractCount(getLoginUserId())); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得合同精简列表", description = "只包含的合同,主要用于前端的下拉选项") + @Parameter(name = "customerId", description = "客户编号", required = true) + @PreAuthorize("@ss.hasPermission('crm:contract:query')") + public CommonResult> getContractSimpleList(@RequestParam("customerId") Long customerId) { + CrmContractPageReqVO pageReqVO = new CrmContractPageReqVO().setCustomerId(customerId); + pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); // 不分页 + PageResult pageResult = contractService.getContractPageByCustomerId(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return success(Collections.emptyList()); } - return CrmContractConvert.INSTANCE.convertList(contractList, userMap, customerList, contactMap, businessMap, contractProductMap, productList); + // 拼接数据 + Map receivablePriceMap = receivableService.getReceivablePriceMapByContractId( + convertSet(pageResult.getList(), CrmContractDO::getId)); + return success(convertList(pageResult.getList(), contract -> new CrmContractRespVO() // 只返回 id、name 等精简字段 + .setId(contract.getId()).setName(contract.getName()).setAuditStatus(contract.getAuditStatus()) + .setTotalPrice(contract.getTotalPrice()) + .setTotalReceivablePrice(receivablePriceMap.getOrDefault(contract.getId(), BigDecimal.ZERO)))); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigRespVO.java new file mode 100644 index 000000000..c39cf9269 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigRespVO.java @@ -0,0 +1,16 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - CRM 合同配置 Response VO") +@Data +public class CrmContractConfigRespVO { + + @Schema(description = "是否开启提前提醒", example = "true") + private Boolean notifyEnabled; + + @Schema(description = "提前提醒天数", example = "2") + private Integer notifyDays; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigSaveReqVO.java new file mode 100644 index 000000000..7ecd650e3 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/config/CrmContractConfigSaveReqVO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config; + +import cn.hutool.core.util.BooleanUtil; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.AssertTrue; +import lombok.Data; + +import java.util.Objects; + +@Schema(description = "管理后台 - CRM 合同配置 Request VO") +@Data +public class CrmContractConfigSaveReqVO { + + @Schema(description = "是否开启提前提醒", example = "true") + @DiffLogField(name = "是否开启提前提醒") + private Boolean notifyEnabled; + + @Schema(description = "提前提醒天数", example = "2") + @DiffLogField(name = "提前提醒天数") + private Integer notifyDays; + + @AssertTrue(message = "提前提醒天数不能为空") + @JsonIgnore + public boolean isNotifyDaysValid() { + if (!BooleanUtil.isTrue(getNotifyEnabled())) { + return true; + } + return Objects.nonNull(getNotifyDays()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractPageReqVO.java similarity index 99% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractPageReqVO.java index c61a64ccf..74f8008b8 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractPageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractPageReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.validation.InEnum; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java similarity index 64% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java index da6239414..a01bc110b 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractRespVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; @@ -6,13 +6,11 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; -import org.springframework.format.annotation.DateTimeFormat; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - @Schema(description = "管理后台 - CRM 合同 Response VO") @Data @ExcelIgnoreUnannotated @@ -26,6 +24,10 @@ public class CrmContractRespVO { @ExcelProperty("合同名称") private String name; + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101") + @ExcelProperty("合同编号") + private String no; + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "18336") @ExcelProperty("客户编号") private Long customerId; @@ -40,72 +42,74 @@ public class CrmContractRespVO { @ExcelProperty("商机名称") private String businessName; + @Schema(description = "最后跟进时间") + @ExcelProperty("最后跟进时间") + private LocalDateTime contactLastTime; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("负责人的用户编号") + private Long ownerUserId; + @Schema(description = "负责人名字", example = "25682") + @ExcelProperty("负责人名字") + private String ownerUserName; + @Schema(description = "负责人部门") + @ExcelProperty("负责人部门") + private String ownerUserDeptName; + @Schema(description = "工作流编号", example = "1043") @ExcelProperty("工作流编号") - private Long processInstanceId; + private String processInstanceId; + + @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @ExcelProperty("审批状态") + private Integer auditStatus; @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED) @ExcelProperty("下单日期") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime orderDate; - @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144") - @ExcelProperty("负责人的用户编号") - private Long ownerUserId; - - // TODO @芋艿:未来应该支持自动生成; - @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101") - @ExcelProperty("合同编号") - private String no; - @Schema(description = "开始时间") @ExcelProperty("开始时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime startTime; @Schema(description = "结束时间") @ExcelProperty("结束时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime endTime; - @Schema(description = "合同金额", example = "5617") - @ExcelProperty("合同金额") - private Integer price; - - @Schema(description = "整单折扣") - @ExcelProperty("整单折扣") - private Integer discountPercent; - @Schema(description = "产品总金额", example = "19510") @ExcelProperty("产品总金额") - private Integer productPrice; + private BigDecimal totalProductPrice; - @Schema(description = "联系人编号", example = "18546") - @ExcelProperty("联系人编号") - private Long contactId; - @Schema(description = "联系人编号", example = "18546") - @ExcelProperty("联系人编号") - private String contactName; + @Schema(description = "整单折扣") + @ExcelProperty("整单折扣") + private BigDecimal discountPercent; + + @Schema(description = "合同金额", example = "5617") + @ExcelProperty("合同金额") + private BigDecimal totalPrice; + + @Schema(description = "已回款金额", example = "5617") + @ExcelProperty("已回款金额") + private BigDecimal totalReceivablePrice; + + @Schema(description = "客户签约人编号", example = "18546") + private Long signContactId; + @Schema(description = "客户签约人", example = "小豆") + @ExcelProperty("客户签约人") + private String signContactName; @Schema(description = "公司签约人", example = "14036") - @ExcelProperty("公司签约人") private Long signUserId; - @Schema(description = "公司签约人", example = "14036") + @Schema(description = "公司签约人", example = "小明") @ExcelProperty("公司签约人") private String signUserName; - @Schema(description = "最后跟进时间") - @ExcelProperty("最后跟进时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime contactLastTime; - @Schema(description = "备注", example = "你猜") @ExcelProperty("备注") private String remark; @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) @ExcelProperty("创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime createTime; @Schema(description = "创建人", example = "25682") @@ -118,47 +122,40 @@ public class CrmContractRespVO { @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) @ExcelProperty("更新时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime updateTime; - @Schema(description = "负责人", example = "test") - @ExcelProperty("负责人") - private String ownerUserName; - - @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") - @ExcelProperty("审批状态") - private Integer auditStatus; - @Schema(description = "产品列表") - private List productItems; + private List products; - // TODO @puhui999:可以直接叫 Item @Schema(description = "产品列表") @Data @NoArgsConstructor @AllArgsConstructor - public static class CrmContractProductItemRespVO { + public static class Product { - @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "888") private Long id; - @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "我是产品") - private String name; + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") + private Long productId; + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + private String productName; + @Schema(description = "产品条码", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") + private String productNo; + @Schema(description = "产品单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四") + private Integer productUnit; - @Schema(description = "产品编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "N881") - private String no; + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") + private BigDecimal productPrice; - @Schema(description = "单位", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") - private Integer unit; + @Schema(description = "合同价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") + private BigDecimal contractPrice; - @Schema(description = "价格,单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "100") - private Integer price; + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") + private BigDecimal count; - @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20") - private Integer count; - - @Schema(description = "产品折扣", example = "99") - private Integer discountPercent; + @Schema(description = "总计价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") + private BigDecimal totalPrice; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractSaveReqVO.java similarity index 76% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractSaveReqVO.java index 20b20580e..47491eef4 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractSaveReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract; import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmBusinessParseFunction; import cn.iocoder.yudao.module.crm.framework.operatelog.core.CrmContactParseFunction; @@ -12,6 +12,7 @@ import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.format.annotation.DateTimeFormat; +import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.List; @@ -38,22 +39,16 @@ public class CrmContractSaveReqVO { @DiffLogField(name = "商机", function = CrmBusinessParseFunction.NAME) private Long businessId; - @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED) - @DiffLogField(name = "下单日期") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - @NotNull(message = "下单日期不能为空") - private LocalDateTime orderDate; - @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17144") @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) @NotNull(message = "负责人不能为空") private Long ownerUserId; - // TODO @芋艿:未来应该支持自动生成; - @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20230101") - @DiffLogField(name = "合同编号") - @NotNull(message = "合同编号不能为空") - private String no; + @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED) + @DiffLogField(name = "下单日期") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + @NotNull(message = "下单日期不能为空") + private LocalDateTime orderDate; @Schema(description = "开始时间") @DiffLogField(name = "开始时间") @@ -65,21 +60,18 @@ public class CrmContractSaveReqVO { @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) private LocalDateTime endTime; + @Schema(description = "整单折扣", requiredMode = Schema.RequiredMode.REQUIRED, example = "55.00") + @DiffLogField(name = "整单折扣") + @NotNull(message = "整单折扣不能为空") + private BigDecimal discountPercent; + @Schema(description = "合同金额", example = "5617") @DiffLogField(name = "合同金额") - private Integer price; + private BigDecimal totalPrice; - @Schema(description = "整单折扣") - @DiffLogField(name = "整单折扣") - private Integer discountPercent; - - @Schema(description = "产品总金额", example = "19510") - @DiffLogField(name = "产品总金额") - private Integer productPrice; - - @Schema(description = "联系人编号", example = "18546") - @DiffLogField(name = "联系人", function = CrmContactParseFunction.NAME) - private Long contactId; + @Schema(description = "客户签约人编号", example = "18546") + @DiffLogField(name = "客户签约人", function = CrmContactParseFunction.NAME) + private Long signContactId; @Schema(description = "公司签约人", example = "14036") @DiffLogField(name = "公司签约人", function = SysAdminUserParseFunction.NAME) @@ -89,27 +81,31 @@ public class CrmContractSaveReqVO { @DiffLogField(name = "备注") private String remark; - @Schema(description = "产品列表") - private List productItems; + private List products; @Schema(description = "产品列表") @Data @NoArgsConstructor @AllArgsConstructor - public static class CrmContractProductItem { + public static class Product { - @Schema(description = "产品编号", example = "20529") + @Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529") @NotNull(message = "产品编号不能为空") - private Long id; + private Long productId; + + @Schema(description = "产品单价", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") + @NotNull(message = "产品单价不能为空") + private BigDecimal productPrice; + + @Schema(description = "合同价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "123.00") + @NotNull(message = "合同价格不能为空") + private BigDecimal contractPrice; @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") @NotNull(message = "产品数量不能为空") private Integer count; - @Schema(description = "产品折扣") - private Integer discountPercent; - } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractTransferReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractTransferReqVO.java similarity index 98% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractTransferReqVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractTransferReqVO.java index 88227c560..4b8245c40 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractTransferReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/contract/CrmContractTransferReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.contract.vo; +package cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract; import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http deleted file mode 100644 index 9a6cb93a8..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.http +++ /dev/null @@ -1,16 +0,0 @@ -### 请求 /transfer -PUT {{baseUrl}}/crm/customer/transfer -Content-Type: application/-id: {{adminTenentId}}json -Authorization: Bearer {{token}} -tenant - -{ - "id": 10, - "newOwnerUserId": 127 -} - -### 自定义日志记录结果 -### 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=客户转移, bizId=10, content=把客户【张三】的负责人从【芋道源码(15612345678)】变更为了【tttt】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/transfer, userIp=127.0.0.1, userAgent=Apache-HttpClient/4.5.14 (Java/17.0.9)) - -### diff 日志 -### | 操作日志 ===> OperateLogV2CreateReqBO(traceId=, userId=1, userType=2, module=CRM-客户, name=更新客户, bizId=11, content=更新了客户【所属行业】从【H 住宿和餐饮业】修改为【D 电力、热力、燃气及水生产和供应业】;【客户等级】从【C (非优先客户)】修改为【A (重点客户)】;【客户来源】从【线上咨询】修改为【预约上门】, requestMethod=PUT, requestUrl=/admin-api/crm/customer/update, userIp=0:0:0:0:0:0:0:1, userAgent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36) diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java index da23f2b47..e784447b2 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java @@ -2,48 +2,55 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; -import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.core.KeyValue; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; +import cn.iocoder.yudao.framework.common.util.number.NumberUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.framework.excel.core.enums.ExcelColumn; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; +import cn.iocoder.yudao.framework.ip.core.Area; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; -import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; +import cn.iocoder.yudao.module.system.api.dict.DictDataApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; -import org.mapstruct.ap.internal.util.Collections; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Stream; -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED; +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*; +import static java.util.Collections.singletonList; @Tag(name = "管理后台 - CRM 客户") @RestController @@ -55,10 +62,13 @@ public class CrmCustomerController { private CrmCustomerService customerService; @Resource private CrmCustomerPoolConfigService customerPoolConfigService; + @Resource private DeptApi deptApi; @Resource private AdminUserApi adminUserApi; + @Resource + private DictDataApi dictDataApi; @PostMapping("/create") @Operation(summary = "创建客户") @@ -75,6 +85,18 @@ public class CrmCustomerController { return success(true); } + @PutMapping("/update-deal-status") + @Operation(summary = "更新客户的成交状态") + @Parameters({ + @Parameter(name = "id", description = "客户编号", required = true), + @Parameter(name = "dealStatus", description = "成交状态", required = true) + }) + public CommonResult updateCustomerDealStatus(@RequestParam("id") Long id, + @RequestParam("dealStatus") Boolean dealStatus) { + customerService.updateCustomerDealStatus(id, dealStatus); + return success(true); + } + @DeleteMapping("/delete") @Operation(summary = "删除客户") @Parameter(name = "id", description = "客户编号", required = true) @@ -91,94 +113,126 @@ public class CrmCustomerController { public CommonResult getCustomer(@RequestParam("id") Long id) { // 1. 获取客户 CrmCustomerDO customer = customerService.getCustomer(id); - if (customer == null) { - return success(null); - } // 2. 拼接数据 - Map userMap = adminUserApi.getUserMap( - Collections.asSet(Long.valueOf(customer.getCreator()), customer.getOwnerUserId())); - Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); - return success(CrmCustomerConvert.INSTANCE.convert(customer, userMap, deptMap)); + return success(buildCustomerDetail(customer)); + } + + public CrmCustomerRespVO buildCustomerDetail(CrmCustomerDO customer) { + if (customer == null) { + return null; + } + return buildCustomerDetailList(singletonList(customer)).get(0); } @GetMapping("/page") @Operation(summary = "获得客户分页") @PreAuthorize("@ss.hasPermission('crm:customer:query')") public CommonResult> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) { + customerService.autoPutCustomerPool(); // 1. 查询客户分页 PageResult pageResult = customerService.getCustomerPage(pageVO, getLoginUserId()); if (CollUtil.isEmpty(pageResult.getList())) { return success(PageResult.empty(pageResult.getTotal())); } - // 2. 拼接数据 - Map poolDayMap = Boolean.TRUE.equals(pageVO.getPool()) ? null : - getPoolDayMap(pageResult.getList()); // 客户界面,需要查看距离进入公海的时间 - Map userMap = adminUserApi.getUserMap( - convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId()))); - Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); - return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, userMap, deptMap, poolDayMap)); + return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal())); } - @GetMapping("/put-in-pool-remind-page") + public List buildCustomerDetailList(List list) { + if (CollUtil.isEmpty(list)) { + return java.util.Collections.emptyList(); + } + // 1.1 获取创建人、负责人列表 + Map userMap = adminUserApi.getUserMap(convertListByFlatMap(list, + contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + // 1.2 获取距离进入公海的时间 + Map poolDayMap = getPoolDayMap(list); + // 2. 转换成 VO + return BeanUtils.toBean(list, CrmCustomerRespVO.class, customerVO -> { + customerVO.setAreaName(AreaUtils.format(customerVO.getAreaId())); + // 2.1 设置创建人、负责人名称 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(customerVO.getCreator()), + user -> customerVO.setCreatorName(user.getNickname())); + MapUtils.findAndThen(userMap, customerVO.getOwnerUserId(), user -> { + customerVO.setOwnerUserName(user.getNickname()); + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> customerVO.setOwnerUserDeptName(dept.getName())); + }); + // 2.2 设置距离进入公海的时间 + if (customerVO.getOwnerUserId() != null) { + customerVO.setPoolDay(poolDayMap.get(customerVO.getId())); + } + }); + } + + @GetMapping("/put-pool-remind-page") @Operation(summary = "获得待进入公海客户分页") @PreAuthorize("@ss.hasPermission('crm:customer:query')") - public CommonResult> getPutInPoolRemindCustomerPage(@Valid CrmCustomerPageReqVO pageVO) { - // 获取公海配置 TODO @dbh52:合并到 getPutInPoolRemindCustomerPage 会更合适哈; - CrmCustomerPoolConfigDO poolConfigDO = customerPoolConfigService.getCustomerPoolConfig(); - if (ObjUtil.isNull(poolConfigDO) - || Boolean.FALSE.equals(poolConfigDO.getEnabled()) - || Boolean.FALSE.equals(poolConfigDO.getNotifyEnabled()) - ) { // TODO @dbh52:这个括号,一般不换行,在 java 这里; - throw exception(CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED); - } - + public CommonResult> getPutPoolRemindCustomerPage(@Valid CrmCustomerPageReqVO pageVO) { // 1. 查询客户分页 - PageResult pageResult = customerService.getPutInPoolRemindCustomerPage(pageVO, poolConfigDO, getLoginUserId()); - if (CollUtil.isEmpty(pageResult.getList())) { - return success(PageResult.empty(pageResult.getTotal())); - } - + PageResult pageResult = customerService.getPutPoolRemindCustomerPage(pageVO, getLoginUserId()); // 2. 拼接数据 - // TODO @芋艿:合并 getCustomerPage 和 getPutInPoolRemindCustomerPage 的后置处理; - Map poolDayMap = getPoolDayMap(pageResult.getList()); // 客户界面,需要查看距离进入公海的时间 - Map userMap = adminUserApi.getUserMap( - convertSetByFlatMap(pageResult.getList(), user -> Stream.of(Long.parseLong(user.getCreator()), user.getOwnerUserId()))); - Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); - return success(CrmCustomerConvert.INSTANCE.convertPage(pageResult, userMap, deptMap, poolDayMap)); + return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal())); + } + + @GetMapping("/put-pool-remind-count") + @Operation(summary = "获得待进入公海客户数量") + @PreAuthorize("@ss.hasPermission('crm:customer:query')") + public CommonResult getPutPoolRemindCustomerCount() { + return success(customerService.getPutPoolRemindCustomerCount(getLoginUserId())); + } + + @GetMapping("/today-contact-count") + @Operation(summary = "获得今日需联系客户数量") + @PreAuthorize("@ss.hasPermission('crm:customer:query')") + public CommonResult getTodayContactCustomerCount() { + return success(customerService.getTodayContactCustomerCount(getLoginUserId())); + } + + @GetMapping("/follow-count") + @Operation(summary = "获得分配给我、待跟进的线索数量的客户数量") + @PreAuthorize("@ss.hasPermission('crm:customer:query')") + public CommonResult getFollowCustomerCount() { + return success(customerService.getFollowCustomerCount(getLoginUserId())); } /** - * 获取距离进入公海的时间 + * 获取距离进入公海的时间 Map * - * @param customerList 客户列表 - * @return Map + * @param list 客户列表 + * @return key 客户编号, value 距离进入公海的时间 */ - private Map getPoolDayMap(List customerList) { + private Map getPoolDayMap(List list) { CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); if (poolConfig == null || !poolConfig.getEnabled()) { return MapUtil.empty(); } - return convertMap(customerList, CrmCustomerDO::getId, customer -> { + list = CollectionUtils.filterList(list, customer -> { + // 特殊:如果没负责人,则说明已经在公海,不用计算 + if (customer.getOwnerUserId() == null) { + return false; + } + // 已成交 or 已锁定,不进入公海 + return !customer.getDealStatus() && !customer.getLockStatus(); + }); + return convertMap(list, CrmCustomerDO::getId, customer -> { // 1.1 未成交放入公海天数 - long dealExpireDay = 0; - if (!customer.getDealStatus()) { - dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime()); - } + long dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getOwnerTime()); // 1.2 未跟进放入公海天数 - LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime()); - long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime); - if (contactExpireDay < 0) { - contactExpireDay = 0; + LocalDateTime lastTime = customer.getOwnerTime(); + if (customer.getContactLastTime() != null && customer.getContactLastTime().isAfter(lastTime)) { + lastTime = customer.getContactLastTime(); } + long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime); // 2. 返回最小的天数 - return Math.min(dealExpireDay, contactExpireDay); + long poolDay = Math.min(dealExpireDay, contactExpireDay); + return poolDay > 0 ? poolDay : 0; }); } - @GetMapping(value = "/list-all-simple") + @GetMapping(value = "/simple-list") @Operation(summary = "获取客户精简信息列表", description = "只包含有读权限的客户,主要用于前端的下拉选项") - public CommonResult> getSimpleDeptList() { + public CommonResult> getCustomerSimpleList() { CrmCustomerPageReqVO reqVO = new CrmCustomerPageReqVO(); reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 List list = customerService.getCustomerPage(reqVO, getLoginUserId()).getList(); @@ -186,7 +240,6 @@ public class CrmCustomerController { new CrmCustomerRespVO().setId(customer.getId()).setName(customer.getName()))); } - // TODO @puhui999:公海的导出,前端可以接下 @GetMapping("/export-excel") @Operation(summary = "导出客户 Excel") @PreAuthorize("@ss.hasPermission('crm:customer:export')") @@ -197,7 +250,7 @@ public class CrmCustomerController { List list = customerService.getCustomerPage(pageVO, getLoginUserId()).getList(); // 导出 Excel ExcelUtils.write(response, "客户.xls", "数据", CrmCustomerRespVO.class, - BeanUtils.toBean(list, CrmCustomerRespVO.class)); + buildCustomerDetailList(list)); } @GetMapping("/get-import-template") @@ -205,15 +258,33 @@ public class CrmCustomerController { public void importTemplate(HttpServletResponse response) throws IOException { // 手动创建导出 demo List list = Arrays.asList( - CrmCustomerImportExcelVO.builder().name("芋道").industryId(1).level(1).source(1).mobile("15601691300").telephone("") - .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("") - .areaId(null).detailAddress("").build(), - CrmCustomerImportExcelVO.builder().name("源码").industryId(1).level(1).source(1).mobile("15601691300").telephone("") - .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("") - .areaId(null).detailAddress("").build() + CrmCustomerImportExcelVO.builder().name("芋道").industryId(1).level(1).source(1) + .mobile("15601691300").telephone("").qq("").wechat("").email("yunai@iocoder.cn") + .areaId(null).detailAddress("").remark("").build(), + CrmCustomerImportExcelVO.builder().name("源码").industryId(1).level(1).source(1) + .mobile("15601691300").telephone("").qq("").wechat("").email("yunai@iocoder.cn") + .areaId(null).detailAddress("").remark("").build() ); // 输出 - ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list); + ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list, builderSelectMap()); + } + + private List>> builderSelectMap() { + List>> selectMap = new ArrayList<>(); + // 获取地区下拉数据 + // TODO @puhui999:嘿嘿,这里改成省份、城市、区域,三个选项,难度大么? + Area area = AreaUtils.parseArea(Area.ID_CHINA); + selectMap.add(new KeyValue<>(ExcelColumn.G, AreaUtils.getAreaNodePathList(area.getChildren()))); + // 获取客户所属行业 + List customerIndustries = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_INDUSTRY); + selectMap.add(new KeyValue<>(ExcelColumn.I, customerIndustries)); + // 获取客户等级 + List customerLevels = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_LEVEL); + selectMap.add(new KeyValue<>(ExcelColumn.J, customerLevels)); + // 获取客户来源 + List customerSources = dictDataApi.getDictDataLabelList(CRM_CUSTOMER_SOURCE); + selectMap.add(new KeyValue<>(ExcelColumn.K, customerSources)); + return selectMap; } @PostMapping("/import") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java index 95f4ccd8f..ea84c9a5e 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerLimitConfigController.java @@ -3,10 +3,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigRespVO; import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigSaveReqVO; -import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerLimitConfigConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerLimitConfigService; import cn.iocoder.yudao.module.system.api.dept.DeptApi; @@ -71,11 +72,14 @@ public class CrmCustomerLimitConfigController { @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('crm:customer-limit-config:query')") public CommonResult getCustomerLimitConfig(@RequestParam("id") Long id) { - CrmCustomerLimitConfigDO customerLimitConfig = customerLimitConfigService.getCustomerLimitConfig(id); + CrmCustomerLimitConfigDO limitConfig = customerLimitConfigService.getCustomerLimitConfig(id); // 拼接数据 - Map userMap = adminUserApi.getUserMap(customerLimitConfig.getUserIds()); - Map deptMap = deptApi.getDeptMap(customerLimitConfig.getDeptIds()); - return success(CrmCustomerLimitConfigConvert.INSTANCE.convert(customerLimitConfig, userMap, deptMap)); + Map userMap = adminUserApi.getUserMap(limitConfig.getUserIds()); + Map deptMap = deptApi.getDeptMap(limitConfig.getDeptIds()); + return success(BeanUtils.toBean(limitConfig, CrmCustomerLimitConfigRespVO.class, configVO -> { + configVO.setUsers(CollectionUtils.convertList(configVO.getUserIds(), userMap::get)); + configVO.setDepts(CollectionUtils.convertList(configVO.getDeptIds(), deptMap::get)); + })); } @GetMapping("/page") @@ -91,7 +95,10 @@ public class CrmCustomerLimitConfigController { convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getUserIds, Collection::stream)); Map deptMap = deptApi.getDeptMap( convertSetByFlatMap(pageResult.getList(), CrmCustomerLimitConfigDO::getDeptIds, Collection::stream)); - return success(CrmCustomerLimitConfigConvert.INSTANCE.convertPage(pageResult, userMap, deptMap)); + return success(BeanUtils.toBean(pageResult, CrmCustomerLimitConfigRespVO.class, configVO -> { + configVO.setUsers(CollectionUtils.convertList(configVO.getUserIds(), userMap::get)); + configVO.setDepts(CollectionUtils.convertList(configVO.getDeptIds(), deptMap::get)); + })); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerDistributeReqVO.java similarity index 98% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerDistributeReqVO.java index 24113ed12..16190baee 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerDistributeReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerDistributeReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java similarity index 82% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java index 4f57564dd..2c39472fd 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportExcelVO.java @@ -1,6 +1,7 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.AreaConvert; import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; import com.alibaba.excel.annotation.ExcelProperty; import lombok.AllArgsConstructor; @@ -24,7 +25,27 @@ public class CrmCustomerImportExcelVO { @ExcelProperty("客户名称") private String name; - // TODO @puhui999:industryId、level、source 字段,可以研究下怎么搞下拉框 + @ExcelProperty("手机") + private String mobile; + + @ExcelProperty("电话") + private String telephone; + + @ExcelProperty("QQ") + private String qq; + + @ExcelProperty("微信") + private String wechat; + + @ExcelProperty("邮箱") + private String email; + + @ExcelProperty(value = "地区", converter = AreaConvert.class) + private Integer areaId; + + @ExcelProperty("详细地址") + private String detailAddress; + @ExcelProperty(value = "所属行业", converter = DictConvert.class) @DictFormat(CRM_CUSTOMER_INDUSTRY) private Integer industryId; @@ -37,35 +58,7 @@ public class CrmCustomerImportExcelVO { @DictFormat(CRM_CUSTOMER_SOURCE) private Integer source; - @ExcelProperty("手机") - private String mobile; - - @ExcelProperty("电话") - private String telephone; - - @ExcelProperty("网址") - private String website; - - @ExcelProperty("QQ") - private String qq; - - @ExcelProperty("微信") - private String wechat; - - @ExcelProperty("邮箱") - private String email; - - @ExcelProperty("客户描述") - private String description; - @ExcelProperty("备注") private String remark; - // TODO @puhui999:需要选择省市区,需要研究下,怎么搞合理点; - @ExcelProperty("地区编号") - private Integer areaId; - - @ExcelProperty("详细地址") - private String detailAddress; - } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportReqVO.java similarity index 98% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportReqVO.java index a396dc50b..3f31b9732 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportRespVO.java similarity index 98% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportRespVO.java index de35b7b92..dda5bc5d1 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerImportRespVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerLockReqVO.java similarity index 97% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerLockReqVO.java index 1cf9ff382..10bf2e10a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerLockReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerLockReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerPageReqVO.java similarity index 72% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerPageReqVO.java index bded50473..73af5d6b1 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerPageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerPageReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.validation.InEnum; @@ -14,6 +14,19 @@ import lombok.ToString; @ToString(callSuper = true) public class CrmCustomerPageReqVO extends PageParam { + /** + * 联系状态 - 今日需联系 + */ + public static final int CONTACT_TODAY = 1; + /** + * 联系状态 - 已逾期 + */ + public static final int CONTACT_EXPIRED = 2; + /** + * 联系状态 - 已联系 + */ + public static final int CONTACT_ALREADY = 3; + @Schema(description = "客户名称", example = "赵六") private String name; @@ -36,4 +49,10 @@ public class CrmCustomerPageReqVO extends PageParam { @Schema(description = "是否为公海数据", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") private Boolean pool; // null 则表示为不是公海数据 + @Schema(description = "联系状态", example = "1") + private Integer contactStatus; // backlog 查询条件 + + @Schema(description = "跟进状态", example = "true") + private Boolean followUpStatus; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java similarity index 84% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java index 69c75856f..236129918 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerRespVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; @@ -7,12 +7,9 @@ import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; import java.time.LocalDateTime; -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - @Schema(description = "管理后台 - CRM 客户 Response VO") @Data @ExcelIgnoreUnannotated @@ -31,6 +28,28 @@ public class CrmCustomerRespVO { @DictFormat(DictTypeConstants.BOOLEAN_STRING) private Boolean followUpStatus; + @Schema(description = "最后跟进时间") + @ExcelProperty("最后跟进时间") + private LocalDateTime contactLastTime; + + @Schema(description = "最后跟进内容", example = "吃饭、睡觉、打逗逗") + @ExcelProperty("最后跟进内容") + private String contactLastContent; + + @Schema(description = "下次联系时间") + @ExcelProperty("下次联系时间") + private LocalDateTime contactNextTime; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("负责人的用户编号") + private Long ownerUserId; + @Schema(description = "负责人名字", example = "25682") + @ExcelProperty("负责人名字") + private String ownerUserName; + @Schema(description = "负责人部门") + @ExcelProperty("负责人部门") + private String ownerUserDeptName; + @Schema(description = "锁定状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") @ExcelProperty(value = "锁定状态", converter = DictConvert.class) @DictFormat(DictTypeConstants.BOOLEAN_STRING) @@ -41,6 +60,36 @@ public class CrmCustomerRespVO { @DictFormat(DictTypeConstants.BOOLEAN_STRING) private Boolean dealStatus; + @Schema(description = "手机", example = "25682") + @ExcelProperty("手机") + private String mobile; + + @Schema(description = "电话", example = "25682") + @ExcelProperty("电话") + private String telephone; + + @Schema(description = "QQ", example = "25682") + @ExcelProperty("QQ") + private String qq; + + @Schema(description = "wechat", example = "25682") + @ExcelProperty("wechat") + private String wechat; + + @Schema(description = "email", example = "25682") + @ExcelProperty("email") + private String email; + + @Schema(description = "地区编号", example = "1024") + @ExcelProperty("地区编号") + private Integer areaId; + @Schema(description = "地区名称", example = "北京市") + @ExcelProperty("地区名称") + private String areaName; + @Schema(description = "详细地址", example = "北京市成华大道") + @ExcelProperty("详细地址") + private String detailAddress; + @Schema(description = "所属行业", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") @ExcelProperty(value = "所属行业", converter = DictConvert.class) @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_INDUSTRY) @@ -56,67 +105,10 @@ public class CrmCustomerRespVO { @DictFormat(cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_CUSTOMER_SOURCE) private Integer source; - @Schema(description = "负责人的用户编号", example = "25682") - @ExcelProperty("手机") - private String mobile; - - @Schema(description = "负责人的用户编号", example = "25682") - @ExcelProperty("电话") - private String telephone; - - @Schema(description = "负责人的用户编号", example = "25682") - @ExcelProperty("网址") - private String website; - - @Schema(description = "负责人的用户编号", example = "25682") - @ExcelProperty("QQ") - private String qq; - - @Schema(description = "负责人的用户编号", example = "25682") - @ExcelProperty("wechat") - private String wechat; - - @Schema(description = "负责人的用户编号", example = "25682") - @ExcelProperty("email") - private String email; - - @Schema(description = "负责人的用户编号", example = "25682") - @ExcelProperty("客户描述") - private String description; - @Schema(description = "负责人的用户编号", example = "25682") @ExcelProperty("备注") private String remark; - @Schema(description = "负责人的用户编号", example = "25682") - @ExcelProperty("负责人的用户编号") - private Long ownerUserId; - @Schema(description = "负责人名字", example = "25682") - @ExcelProperty("负责人名字") - private String ownerUserName; - @Schema(description = "负责人部门") - @ExcelProperty("负责人部门") - private String ownerUserDeptName; - - @Schema(description = "地区编号", example = "1024") - @ExcelProperty("地区编号") - private Integer areaId; - @Schema(description = "地区名称", example = "北京市") - @ExcelProperty("地区名称") - private String areaName; - @Schema(description = "详细地址", example = "北京市成华大道") - @ExcelProperty("详细地址") - private String detailAddress; - - @Schema(description = "最后跟进时间") - @ExcelProperty("最后跟进时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime contactLastTime; - - @Schema(description = "下次联系时间") - @ExcelProperty("下次联系时间") - private LocalDateTime contactNextTime; - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) @ExcelProperty("创建时间") private LocalDateTime createTime; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerSaveReqVO.java similarity index 91% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerSaveReqVO.java index d6d73b142..bf4a4f00f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerSaveReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.framework.common.validation.Mobile; @@ -13,6 +13,7 @@ import com.mzt.logapi.starter.annotation.DiffLogField; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; @@ -34,19 +35,14 @@ public class CrmCustomerSaveReqVO { @NotEmpty(message = "客户名称不能为空") private String name; - @Schema(description = "所属行业", example = "1") - @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME) - @DictFormat(CRM_CUSTOMER_INDUSTRY) - private Integer industryId; + @Schema(description = "下次联系时间") + @DiffLogField(name = "下次联系时间") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime contactNextTime; - @Schema(description = "客户等级", example = "2") - @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME) - @InEnum(CrmCustomerLevelEnum.class) - private Integer level; - - @Schema(description = "客户来源", example = "3") - @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME) - private Integer source; + @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + @NotNull(message = "负责人的用户编号不能为空") + private Long ownerUserId; @Schema(description = "手机", example = "18000000000") @DiffLogField(name = "手机") @@ -58,10 +54,6 @@ public class CrmCustomerSaveReqVO { @Telephone private String telephone; - @Schema(description = "网址", example = "https://www.baidu.com") - @DiffLogField(name = "网址") - private String website; - @Schema(description = "QQ", example = "123456789") @DiffLogField(name = "QQ") @Size(max = 20, message = "QQ长度不能超过 20 个字符") @@ -78,15 +70,6 @@ public class CrmCustomerSaveReqVO { @Size(max = 255, message = "邮箱长度不能超过 255 个字符") private String email; - @Schema(description = "客户描述", example = "任意文字") - @DiffLogField(name = "客户描述") - @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符") - private String description; - - @Schema(description = "备注", example = "随便") - @DiffLogField(name = "备注") - private String remark; - @Schema(description = "地区编号", example = "20158") @DiffLogField(name = "地区编号", function = SysAreaParseFunction.NAME) private Integer areaId; @@ -95,12 +78,22 @@ public class CrmCustomerSaveReqVO { @DiffLogField(name = "详细地址") private String detailAddress; - @Schema(description = "下次联系时间") - @DiffLogField(name = "下次联系时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime contactNextTime; + @Schema(description = "所属行业", example = "1") + @DiffLogField(name = "所属行业", function = CrmCustomerIndustryParseFunction.NAME) + @DictFormat(CRM_CUSTOMER_INDUSTRY) + private Integer industryId; - @Schema(description = "负责人的用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") - private Long ownerUserId; + @Schema(description = "客户等级", example = "2") + @DiffLogField(name = "客户等级", function = CrmCustomerLevelParseFunction.NAME) + @InEnum(CrmCustomerLevelEnum.class) + private Integer level; + + @Schema(description = "客户来源", example = "3") + @DiffLogField(name = "客户来源", function = CrmCustomerSourceParseFunction.NAME) + private Integer source; + + @Schema(description = "备注", example = "随便") + @DiffLogField(name = "备注") + private String remark; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerTransferReqVO.java similarity index 99% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerTransferReqVO.java index 9bdc43532..e62a84fd7 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerTransferReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/customer/CrmCustomerTransferReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import io.swagger.v3.oas.annotations.media.Schema; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java index f0b726353..64026f15d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordRespVO; import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO; @@ -13,6 +14,8 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; @@ -26,7 +29,7 @@ import java.util.ArrayList; import java.util.Map; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @@ -44,6 +47,9 @@ public class CrmFollowUpRecordController { @Resource private CrmBusinessService businessService; + @Resource + private AdminUserApi adminUserApi; + @PostMapping("/create") @Operation(summary = "创建跟进记录") @PreAuthorize("@ss.hasPermission('crm:follow-up-record:create')") @@ -74,17 +80,24 @@ public class CrmFollowUpRecordController { @PreAuthorize("@ss.hasPermission('crm:follow-up-record:query')") public CommonResult> getFollowUpRecordPage(@Valid CrmFollowUpRecordPageReqVO pageReqVO) { PageResult pageResult = followUpRecordService.getFollowUpRecordPage(pageReqVO); - /// 拼接数据 - Map contactMap = convertMap(contactService.getContactListByIds( - convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream())), CrmContactDO::getId); - Map businessMap = convertMap(businessService.getBusinessList( - convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream())), CrmBusinessDO::getId); + // 1.1 查询联系人和商机 + Map contactMap = contactService.getContactMap( + convertSetByFlatMap(pageResult.getList(), item -> item.getContactIds().stream())); + Map businessMap = businessService.getBusinessMap( + convertSetByFlatMap(pageResult.getList(), item -> item.getBusinessIds().stream())); + // 1.2 查询用户 + Map userMap = adminUserApi.getUserMap( + convertSet(pageResult.getList(), item -> Long.valueOf(item.getCreator()))); + // 2. 拼接数据 PageResult voPageResult = BeanUtils.toBean(pageResult, CrmFollowUpRecordRespVO.class, record -> { - record.setContactNames(new ArrayList<>()).setBusinessNames(new ArrayList<>()); - record.getContactIds().forEach(id -> MapUtils.findAndThen(contactMap, id, - contact -> record.getContactNames().add(contact.getName()))); - record.getContactIds().forEach(id -> MapUtils.findAndThen(businessMap, id, - business -> record.getBusinessNames().add(business.getName()))); + // 2.1 设置联系人和商机信息 + record.setBusinesses(new ArrayList<>()).setContacts(new ArrayList<>()); + record.getContactIds().forEach(id -> MapUtils.findAndThen(contactMap, id, contact -> + record.getContacts().add(new CrmBusinessRespVO().setId(contact.getId()).setName(contact.getName())))); + record.getContactIds().forEach(id -> MapUtils.findAndThen(businessMap, id, business -> + record.getBusinesses().add(new CrmBusinessRespVO().setId(business.getId()).setName(business.getName())))); + // 2.2 设置用户信息 + MapUtils.findAndThen(userMap, Long.valueOf(record.getCreator()), user -> record.setCreatorName(user.getNickname())); }); return success(voPageResult); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java index 83bfd9edc..1ce10b73e 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.crm.controller.admin.followup.vo; import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -36,19 +38,26 @@ public class CrmFollowUpRecordRespVO { @Schema(description = "关联的商机编号数组") private List businessIds; - @Schema(description = "关联的商机名称数组") - private List businessNames; + @Schema(description = "关联的商机数组") + private List businesses; @Schema(description = "关联的联系人编号数组") private List contactIds; @Schema(description = "关联的联系人名称数组") - private List contactNames; + private List contacts; @Schema(description = "图片") private List picUrls; @Schema(description = "附件") private List fileUrls; + @Schema(description = "创建人", example = "1024") + @ExcelProperty("创建人") + private String creator; + @Schema(description = "创建人名字", example = "芋道源码") + @ExcelProperty("创建人名字") + private String creatorName; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java index 982ad3c0b..a94a32cf5 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogV2RespVO; +import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogRespVO; import cn.iocoder.yudao.module.crm.enums.LogRecordConstants; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.system.api.logger.OperateLogApi; @@ -41,7 +41,7 @@ public class CrmOperateLogController { private static final Map BIZ_TYPE_MAP = new HashMap<>(); static { - BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_LEADS.getType(), CRM_LEADS_TYPE); + BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CLUE.getType(), CRM_CLUE_TYPE); BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CUSTOMER.getType(), CRM_CUSTOMER_TYPE); BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_CONTACT.getType(), CRM_CONTACT_TYPE); BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_BUSINESS.getType(), CRM_BUSINESS_TYPE); @@ -54,11 +54,11 @@ public class CrmOperateLogController { @GetMapping("/page") @Operation(summary = "获得操作日志") @PreAuthorize("@ss.hasPermission('crm:operate-log:query')") - public CommonResult> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) { + public CommonResult> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) { OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO(); reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页,需要分页需注释 reqDTO.setBizType(BIZ_TYPE_MAP.get(pageReqVO.getBizType())).setBizId(pageReqVO.getBizId()); - return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO), CrmOperateLogV2RespVO.class)); + return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO), CrmOperateLogRespVO.class)); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogRespVO.java similarity index 93% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogRespVO.java index b3405428f..8e458a8a0 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogRespVO.java @@ -6,10 +6,10 @@ import lombok.Data; import java.time.LocalDateTime; -@Schema(description = "管理后台 - CRM 跟进 Response VO") +@Schema(description = "管理后台 - CRM 操作日志 Response VO") @Data @ExcelIgnoreUnannotated -public class CrmOperateLogV2RespVO { +public class CrmOperateLogRespVO { @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") private Long id; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java index 88b4b3011..428bd07ad 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/permission/CrmPermissionController.java @@ -3,11 +3,11 @@ package cn.iocoder.yudao.module.crm.controller.admin.permission; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO; import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; -import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; @@ -19,6 +19,7 @@ import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import com.google.common.collect.Multimaps; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; @@ -29,11 +30,16 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.*; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - CRM 数据权限") @@ -98,18 +104,32 @@ public class CrmPermissionController { @PreAuthorize("@ss.hasPermission('crm:permission:query')") public CommonResult> getPermissionList(@RequestParam("bizType") Integer bizType, @RequestParam("bizId") Long bizId) { - List permission = permissionService.getPermissionListByBiz(bizType, bizId); - if (CollUtil.isEmpty(permission)) { + List permissions = permissionService.getPermissionListByBiz(bizType, bizId); + if (CollUtil.isEmpty(permissions)) { return success(Collections.emptyList()); } + // 查询相关数据 + Map userMap = adminUserApi.getUserMap( + convertSet(permissions, CrmPermissionDO::getUserId)); + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + Map postMap = postApi.getPostMap( + convertSetByFlatMap(userMap.values(), AdminUserRespDTO::getPostIds, + item -> item != null ? item.stream() : Stream.empty())); // 拼接数据 - List userList = adminUserApi.getUserList(convertSet(permission, CrmPermissionDO::getUserId)); - Map deptMap = deptApi.getDeptMap(convertSet(userList, AdminUserRespDTO::getDeptId)); - Set postIds = CollectionUtils.convertSetByFlatMap(userList, AdminUserRespDTO::getPostIds, - item -> item != null ? item.stream() : Stream.empty()); - Map postMap = postApi.getPostMap(postIds); - return success(CrmPermissionConvert.INSTANCE.convert(permission, userList, deptMap, postMap)); + return success(CollectionUtils.convertList(BeanUtils.toBean(permissions, CrmPermissionRespVO.class), item -> { + findAndThen(userMap, item.getUserId(), user -> { + item.setNickname(user.getNickname()); + findAndThen(deptMap, user.getDeptId(), deptRespDTO -> item.setDeptName(deptRespDTO.getName())); + if (CollUtil.isEmpty(user.getPostIds())) { + item.setPostNames(Collections.emptySet()); + return; + } + List postList = MapUtils.getList(Multimaps.forMap(postMap), user.getPostIds()); + item.setPostNames(CollectionUtils.convertSet(postList, PostRespDTO::getName)); + }); + return item; + })); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java index 94774373d..91d31de7a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/CrmProductController.java @@ -1,16 +1,17 @@ package cn.iocoder.yudao.module.crm.controller.admin.product; import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.SetUtils; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO; import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductSaveReqVO; -import cn.iocoder.yudao.module.crm.convert.product.CrmProductConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; import cn.iocoder.yudao.module.crm.service.product.CrmProductCategoryService; @@ -34,10 +35,9 @@ import java.util.Map; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; +import static java.util.Collections.singletonList; @Tag(name = "管理后台 - CRM 产品") @RestController @@ -49,6 +49,7 @@ public class CrmProductController { private CrmProductService productService; @Resource private CrmProductCategoryService productCategoryService; + @Resource private AdminUserApi adminUserApi; @@ -82,21 +83,30 @@ public class CrmProductController { @PreAuthorize("@ss.hasPermission('crm:product:query')") public CommonResult getProduct(@RequestParam("id") Long id) { CrmProductDO product = productService.getProduct(id); + return success(buildProductDetail(product)); + } + + private CrmProductRespVO buildProductDetail(CrmProductDO product) { if (product == null) { - return success(null); + return null; } - Map userMap = adminUserApi.getUserMap( - SetUtils.asSet(Long.valueOf(product.getCreator()), product.getOwnerUserId())); - CrmProductCategoryDO category = productCategoryService.getProductCategory(product.getCategoryId()); - return success(CrmProductConvert.INSTANCE.convert(product, userMap, category)); + return buildProductDetailList(singletonList(product)).get(0); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得产品精简列表", description = "只包含被开启的产品,主要用于前端的下拉选项") + public CommonResult> getProductSimpleList() { + List list = productService.getProductListByStatus(CommonStatusEnum.ENABLE.getStatus()); + return success(convertList(list, product -> new CrmProductRespVO().setId(product.getId()).setName(product.getName()) + .setUnit(product.getUnit()).setNo(product.getNo()).setPrice(product.getPrice()))); } @GetMapping("/page") @Operation(summary = "获得产品分页") @PreAuthorize("@ss.hasPermission('crm:product:query')") public CommonResult> getProductPage(@Valid CrmProductPageReqVO pageVO) { - PageResult pageResult = productService.getProductPage(pageVO, getLoginUserId()); - return success(new PageResult<>(getProductDetailList(pageResult.getList()), pageResult.getTotal())); + PageResult pageResult = productService.getProductPage(pageVO); + return success(new PageResult<>(buildProductDetailList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/export-excel") @@ -106,21 +116,30 @@ public class CrmProductController { public void exportProductExcel(@Valid CrmProductPageReqVO exportReqVO, HttpServletResponse response) throws IOException { exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE); - List list = productService.getProductPage(exportReqVO, getLoginUserId()).getList(); + List list = productService.getProductPage(exportReqVO).getList(); // 导出 Excel ExcelUtils.write(response, "产品.xls", "数据", CrmProductRespVO.class, - getProductDetailList(list)); + buildProductDetailList(list)); } - private List getProductDetailList(List list) { + private List buildProductDetailList(List list) { if (CollUtil.isEmpty(list)) { return Collections.emptyList(); } + // 1.1 获得用户信息 Map userMap = adminUserApi.getUserMap( convertSetByFlatMap(list, user -> Stream.of(Long.valueOf(user.getCreator()), user.getOwnerUserId()))); - List productCategoryList = productCategoryService.getProductCategoryList( + // 1.2 获得分类信息 + Map categoryMap = productCategoryService.getProductCategoryMap( convertSet(list, CrmProductDO::getCategoryId)); - return CrmProductConvert.INSTANCE.convertList(list, userMap, productCategoryList); + // 2. 拼接数据 + return BeanUtils.toBean(list, CrmProductRespVO.class, productVO -> { + // 2.1 设置用户信息 + MapUtils.findAndThen(userMap, productVO.getOwnerUserId(), user -> productVO.setOwnerUserName(user.getNickname())); + MapUtils.findAndThen(userMap, Long.valueOf(productVO.getCreator()), user -> productVO.setCreatorName(user.getNickname())); + // 2.2 设置分类名称 + MapUtils.findAndThen(categoryMap, productVO.getCategoryId(), category -> productVO.setCategoryName(category.getName())); + }); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java index ceca3e5a0..1f659aa77 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductRespVO.java @@ -8,6 +8,7 @@ import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; +import java.math.BigDecimal; import java.time.LocalDateTime; @Schema(description = "管理后台 - CRM 产品 Response VO") @@ -34,7 +35,7 @@ public class CrmProductRespVO { @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") @ExcelProperty("价格,单位:分") - private Long price; + private BigDecimal price; @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架") @ExcelProperty(value = "单位", converter = DictConvert.class) diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java index 01b2ae443..ef6d5824d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/product/vo/product/CrmProductSaveReqVO.java @@ -7,6 +7,8 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import lombok.Data; +import java.math.BigDecimal; + @Schema(description = "管理后台 - CRM 产品创建/修改 Request VO") @Data public class CrmProductSaveReqVO { @@ -28,10 +30,10 @@ public class CrmProductSaveReqVO { @DiffLogField(name = "单位", function = CrmProductUnitParseFunction.NAME) private Integer unit; - @Schema(description = "价格, 单位:分", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") + @Schema(description = "价格", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") @NotNull(message = "价格不能为空") @DiffLogField(name = "价格") - private Long price; + private BigDecimal price; @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "上架") @NotNull(message = "状态不能为空") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java index 8516ebd66..37cdbb769 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivableController.java @@ -4,20 +4,23 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService; +import cn.iocoder.yudao.module.system.api.dept.DeptApi; +import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; @@ -31,14 +34,15 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @@ -57,18 +61,20 @@ public class CrmReceivableController { @Resource private AdminUserApi adminUserApi; + @Resource + private DeptApi deptApi; @PostMapping("/create") @Operation(summary = "创建回款") @PreAuthorize("@ss.hasPermission('crm:receivable:create')") - public CommonResult createReceivable(@Valid @RequestBody CrmReceivableCreateReqVO createReqVO) { - return success(receivableService.createReceivable(createReqVO, getLoginUserId())); + public CommonResult createReceivable(@Valid @RequestBody CrmReceivableSaveReqVO createReqVO) { + return success(receivableService.createReceivable(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新回款") @PreAuthorize("@ss.hasPermission('crm:receivable:update')") - public CommonResult updateReceivable(@Valid @RequestBody CrmReceivableUpdateReqVO updateReqVO) { + public CommonResult updateReceivable(@Valid @RequestBody CrmReceivableSaveReqVO updateReqVO) { receivableService.updateReceivable(updateReqVO); return success(true); } @@ -88,7 +94,14 @@ public class CrmReceivableController { @PreAuthorize("@ss.hasPermission('crm:receivable:query')") public CommonResult getReceivable(@RequestParam("id") Long id) { CrmReceivableDO receivable = receivableService.getReceivable(id); - return success(CrmReceivableConvert.INSTANCE.convert(receivable)); + return success(buildReceivableDetail(receivable)); + } + + private CrmReceivableRespVO buildReceivableDetail(CrmReceivableDO receivable) { + if (receivable == null) { + return null; + } + return buildReceivableDetailList(Collections.singletonList(receivable)).get(0); } @GetMapping("/page") @@ -96,7 +109,7 @@ public class CrmReceivableController { @PreAuthorize("@ss.hasPermission('crm:receivable:query')") public CommonResult> getReceivablePage(@Valid CrmReceivablePageReqVO pageReqVO) { PageResult pageResult = receivableService.getReceivablePage(pageReqVO, getLoginUserId()); - return success(buildReceivableDetailPage(pageResult)); + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/page-by-customer") @@ -104,10 +117,9 @@ public class CrmReceivableController { public CommonResult> getReceivablePageByCustomer(@Valid CrmReceivablePageReqVO pageReqVO) { Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空"); PageResult pageResult = receivableService.getReceivablePageByCustomerId(pageReqVO); - return success(buildReceivableDetailPage(pageResult)); + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal())); } - // TODO 芋艿:后面在优化导出 @GetMapping("/export-excel") @Operation(summary = "导出回款 Excel") @PreAuthorize("@ss.hasPermission('crm:receivable:export')") @@ -115,33 +127,56 @@ public class CrmReceivableController { public void exportReceivableExcel(@Valid CrmReceivablePageReqVO exportReqVO, HttpServletResponse response) throws IOException { exportReqVO.setPageSize(PAGE_SIZE_NONE); - PageResult pageResult = receivableService.getReceivablePage(exportReqVO, getLoginUserId()); + List list = receivableService.getReceivablePage(exportReqVO, getLoginUserId()).getList(); // 导出 Excel ExcelUtils.write(response, "回款.xls", "数据", CrmReceivableRespVO.class, - buildReceivableDetailPage(pageResult).getList()); + buildReceivableDetailList(list)); } - /** - * 构建详细的回款分页结果 - * - * @param pageResult 简单的回款分页结果 - * @return 详细的回款分页结果 - */ - private PageResult buildReceivableDetailPage(PageResult pageResult) { - List receivableList = pageResult.getList(); + private List buildReceivableDetailList(List receivableList) { if (CollUtil.isEmpty(receivableList)) { - return PageResult.empty(pageResult.getTotal()); + return Collections.emptyList(); } - // 1. 获取客户列表 - List customerList = customerService.getCustomerList( + // 1.1 获取客户列表 + Map customerMap = customerService.getCustomerMap( convertSet(receivableList, CrmReceivableDO::getCustomerId)); - // 2. 获取创建人、负责人列表 + // 1.2 获取创建人、负责人列表 Map userMap = adminUserApi.getUserMap(convertListByFlatMap(receivableList, contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); - // 3. 获得合同列表 - List contractList = contractService.getContractList( + Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); + // 1.3 获得合同列表 + Map contractMap = contractService.getContractMap( convertSet(receivableList, CrmReceivableDO::getContractId)); - return CrmReceivableConvert.INSTANCE.convertPage(pageResult, userMap, customerList, contractList); + // 2. 拼接结果 + return BeanUtils.toBean(receivableList, CrmReceivableRespVO.class, (receivableVO) -> { + // 2.1 拼接客户名称 + findAndThen(customerMap, receivableVO.getCustomerId(), customer -> receivableVO.setCustomerName(customer.getName())); + // 2.2 拼接负责人、创建人名称 + MapUtils.findAndThen(userMap, NumberUtils.parseLong(receivableVO.getCreator()), + user -> receivableVO.setCreatorName(user.getNickname())); + MapUtils.findAndThen(userMap, receivableVO.getOwnerUserId(), user -> { + receivableVO.setOwnerUserName(user.getNickname()); + MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> receivableVO.setOwnerUserDeptName(dept.getName())); + }); + // 2.3 拼接合同信息 + findAndThen(contractMap, receivableVO.getContractId(), contract -> + receivableVO.setContract(BeanUtils.toBean(contract, CrmContractRespVO.class))); + }); + } + + @PutMapping("/submit") + @Operation(summary = "提交回款审批") + @PreAuthorize("@ss.hasPermission('crm:receivable:update')") + public CommonResult submitContract(@RequestParam("id") Long id) { + receivableService.submitReceivable(id, getLoginUserId()); + return success(true); + } + + @GetMapping("/audit-count") + @Operation(summary = "获得待审核回款数量") + @PreAuthorize("@ss.hasPermission('crm:receivable:query')") + public CommonResult getAuditReceivableCount() { + return success(receivableService.getAuditReceivableCount(getLoginUserId())); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java index 252d714f4..73b5b5292 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/CrmReceivablePlanController.java @@ -5,13 +5,13 @@ import cn.hutool.core.lang.Assert; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils; import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; @@ -24,24 +24,25 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; -import org.springframework.context.annotation.Lazy; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; +import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; @@ -56,7 +57,6 @@ public class CrmReceivablePlanController { @Resource private CrmReceivableService receivableService; @Resource - @Lazy private CrmContractService contractService; @Resource private CrmCustomerService customerService; @@ -67,14 +67,14 @@ public class CrmReceivablePlanController { @PostMapping("/create") @Operation(summary = "创建回款计划") @PreAuthorize("@ss.hasPermission('crm:receivable-plan:create')") - public CommonResult createReceivablePlan(@Valid @RequestBody CrmReceivablePlanCreateReqVO createReqVO) { - return success(receivablePlanService.createReceivablePlan(createReqVO, getLoginUserId())); + public CommonResult createReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO createReqVO) { + return success(receivablePlanService.createReceivablePlan(createReqVO)); } @PutMapping("/update") @Operation(summary = "更新回款计划") @PreAuthorize("@ss.hasPermission('crm:receivable-plan:update')") - public CommonResult updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanUpdateReqVO updateReqVO) { + public CommonResult updateReceivablePlan(@Valid @RequestBody CrmReceivablePlanSaveReqVO updateReqVO) { receivablePlanService.updateReceivablePlan(updateReqVO); return success(true); } @@ -94,7 +94,14 @@ public class CrmReceivablePlanController { @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')") public CommonResult getReceivablePlan(@RequestParam("id") Long id) { CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(id); - return success(CrmReceivablePlanConvert.INSTANCE.convert(receivablePlan)); + return success(buildReceivablePlanDetail(receivablePlan)); + } + + private CrmReceivablePlanRespVO buildReceivablePlanDetail(CrmReceivablePlanDO receivablePlan) { + if (receivablePlan == null) { + return null; + } + return buildReceivableDetailList(Collections.singletonList(receivablePlan)).get(0); } @GetMapping("/page") @@ -102,7 +109,7 @@ public class CrmReceivablePlanController { @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')") public CommonResult> getReceivablePlanPage(@Valid CrmReceivablePlanPageReqVO pageReqVO) { PageResult pageResult = receivablePlanService.getReceivablePlanPage(pageReqVO, getLoginUserId()); - return success(convertDetailReceivablePlanPage(pageResult)); + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal())); } @GetMapping("/page-by-customer") @@ -110,10 +117,9 @@ public class CrmReceivablePlanController { public CommonResult> getReceivablePlanPageByCustomer(@Valid CrmReceivablePlanPageReqVO pageReqVO) { Assert.notNull(pageReqVO.getCustomerId(), "客户编号不能为空"); PageResult pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO); - return success(convertDetailReceivablePlanPage(pageResult)); + return success(new PageResult<>(buildReceivableDetailList(pageResult.getList()), pageResult.getTotal())); } - // TODO 芋艿:后面在优化导出 @GetMapping("/export-excel") @Operation(summary = "导出回款计划 Excel") @PreAuthorize("@ss.hasPermission('crm:receivable-plan:export')") @@ -121,36 +127,64 @@ public class CrmReceivablePlanController { public void exportReceivablePlanExcel(@Valid CrmReceivablePlanPageReqVO exportReqVO, HttpServletResponse response) throws IOException { exportReqVO.setPageSize(PAGE_SIZE_NONE); - PageResult pageResult = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId()); + List list = receivablePlanService.getReceivablePlanPage(exportReqVO, getLoginUserId()).getList(); // 导出 Excel ExcelUtils.write(response, "回款计划.xls", "数据", CrmReceivablePlanRespVO.class, - convertDetailReceivablePlanPage(pageResult).getList()); + buildReceivableDetailList(list)); } - /** - * 构建详细的回款计划分页结果 - * - * @param pageResult 简单的回款计划分页结果 - * @return 详细的回款计划分页结果 - */ - private PageResult convertDetailReceivablePlanPage(PageResult pageResult) { - List receivablePlanList = pageResult.getList(); + private List buildReceivableDetailList(List receivablePlanList) { if (CollUtil.isEmpty(receivablePlanList)) { - return PageResult.empty(pageResult.getTotal()); + return Collections.emptyList(); } - // 1. 获取客户列表 - List customerList = customerService.getCustomerList( + // 1.1 获取客户 Map + Map customerMap = customerService.getCustomerMap( convertSet(receivablePlanList, CrmReceivablePlanDO::getCustomerId)); - // 2. 获取创建人、负责人列表 + // 1.2 获取创建人、负责人列表 Map userMap = adminUserApi.getUserMap(convertListByFlatMap(receivablePlanList, contact -> Stream.of(NumberUtils.parseLong(contact.getCreator()), contact.getOwnerUserId()))); - // 3. 获得合同列表 - List contractList = contractService.getContractList( + // 1.3 获得合同 Map + Map contractMap = contractService.getContractMap( convertSet(receivablePlanList, CrmReceivablePlanDO::getContractId)); - // 4. 获得还款列表 - List receivableList = receivableService.getReceivableList( + // 1.4 获得回款 Map + Map receivableMap = receivableService.getReceivableMap( convertSet(receivablePlanList, CrmReceivablePlanDO::getReceivableId)); - return CrmReceivablePlanConvert.INSTANCE.convertPage(pageResult, userMap, customerList, contractList, receivableList); + // 2. 拼接数据 + return BeanUtils.toBean(receivablePlanList, CrmReceivablePlanRespVO.class, (receivablePlanVO) -> { + // 2.1 拼接客户信息 + findAndThen(customerMap, receivablePlanVO.getCustomerId(), customer -> receivablePlanVO.setCustomerName(customer.getName())); + // 2.2 拼接用户信息 + findAndThen(userMap, receivablePlanVO.getOwnerUserId(), user -> receivablePlanVO.setOwnerUserName(user.getNickname())); + findAndThen(userMap, Long.parseLong(receivablePlanVO.getCreator()), user -> receivablePlanVO.setCreatorName(user.getNickname())); + // 2.3 拼接合同信息 + findAndThen(contractMap, receivablePlanVO.getContractId(), contract -> receivablePlanVO.setContractNo(contract.getNo())); + // 2.4 拼接回款信息 + receivablePlanVO.setReceivable(BeanUtils.toBean(receivableMap.get(receivablePlanVO.getReceivableId()), CrmReceivableRespVO.class)); + }); + } + + @GetMapping("/simple-list") + @Operation(summary = "获得回款计划精简列表", description = "获得回款计划精简列表,主要用于前端的下拉选项") + @Parameters({ + @Parameter(name = "customerId", description = "客户编号", required = true), + @Parameter(name = "contractId", description = "合同编号", required = true) + }) + @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')") + public CommonResult> getReceivablePlanSimpleList(@RequestParam("customerId") Long customerId, + @RequestParam("contractId") Long contractId) { + CrmReceivablePlanPageReqVO pageReqVO = new CrmReceivablePlanPageReqVO().setCustomerId(customerId).setContractId(contractId); + pageReqVO.setPageNo(PAGE_SIZE_NONE); + PageResult pageResult = receivablePlanService.getReceivablePlanPageByCustomerId(pageReqVO); + return success(convertList(pageResult.getList(), receivablePlan -> new CrmReceivablePlanRespVO() // 只返回 id、period 等信息 + .setId(receivablePlan.getId()).setPeriod(receivablePlan.getPeriod()).setReceivableId(receivablePlan.getReceivableId()) + .setPrice(receivablePlan.getPrice()).setReturnType(receivablePlan.getReturnType()))); + } + + @GetMapping("/remind-count") + @Operation(summary = "获得待回款提醒数量") + @PreAuthorize("@ss.hasPermission('crm:receivable-plan:query')") + public CommonResult getReceivablePlanRemindCount() { + return success(receivablePlanService.getReceivablePlanRemindCount(getLoginUserId())); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanBaseVO.java deleted file mode 100644 index 70272b8e8..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanBaseVO.java +++ /dev/null @@ -1,54 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -/** - * 回款计划 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ -@Data -public class CrmReceivablePlanBaseVO { - - @Schema(description = "期数", example = "1") - private Integer period; - - @Schema(description = "回款计划编号", example = "19852") - private Long receivableId; - - @Schema(description = "计划回款金额", example = "29675") - private Integer price; - - @Schema(description = "计划回款日期") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime returnTime; - - @Schema(description = "提前几天提醒") - private Integer remindDays; - - @Schema(description = "提醒日期") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime remindTime; - - @Schema(description = "客户名称", example = "18026") - private Long customerId; - - @Schema(description = "合同编号", example = "3473") - private Long contractId; - - // TODO @liuhongfeng:负责人编号 - @Schema(description = "负责人编号", example = "17828") - private Long ownerUserId; - - @Schema(description = "显示顺序") - private Integer sort; - - @Schema(description = "备注", example = "备注") - private String remark; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanCreateReqVO.java deleted file mode 100644 index 193a44bf4..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanCreateReqVO.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; - -import lombok.*; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "管理后台 - CRM 回款计划创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivablePlanCreateReqVO extends CrmReceivablePlanBaseVO { - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java index 3675fba1f..b730128ce 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanPageReqVO.java @@ -30,8 +30,10 @@ public class CrmReceivablePlanPageReqVO extends PageParam { @Schema(description = "客户编号", example = "18026") private Long customerId; - // TODO @芋艿:这个搜的应该是合同编号 no - @Schema(description = "合同名称", example = "3473") + @Schema(description = "合同编号", example = "H3473") + private String contractNo; + + @Schema(description = "合同编号", example = "3473") private Long contractId; @Schema(description = "场景类型", example = "1") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java index d5e9de187..1c58766e1 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanRespVO.java @@ -1,40 +1,75 @@ package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; +import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.Data; + +import java.math.BigDecimal; import java.time.LocalDateTime; +// TODO @puhui999:缺导出 @Schema(description = "管理后台 - CRM 回款计划 Response VO") @Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivablePlanRespVO extends CrmReceivablePlanBaseVO { +public class CrmReceivablePlanRespVO { - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25153") + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") private Long id; - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; + @Schema(description = "期数", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Integer period; + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long customerId; @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test") private String customerName; + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long contractId; @Schema(description = "合同编号", example = "Q110") private String contractNo; + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long ownerUserId; @Schema(description = "负责人", example = "test") private String ownerUserName; - @Schema(description = "创建人", example = "25682") - private String creator; + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") + private LocalDateTime returnTime; - @Schema(description = "创建人名字", example = "test") - private String creatorName; - - @Schema(description = "完成状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") - private Boolean finishStatus; - - @Schema(description = "回款方式", example = "1") // 来自 Receivable 的 returnType 字段 + @Schema(description = "计划回款方式", example = "1") private Integer returnType; + @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") + private BigDecimal price; + + @Schema(description = "回款编号", example = "19852") + private Long receivableId; + @Schema(description = "回款信息") + private CrmReceivableRespVO receivable; + + @Schema(description = "提前几天提醒", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer remindDays; + + @Schema(description = "提醒日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") + private LocalDateTime remindTime; + + @Schema(description = "备注", example = "备注") + private String remark; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("创建时间") + private LocalDateTime createTime; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + @ExcelProperty("更新时间") + private LocalDateTime updateTime; + + @Schema(description = "创建人", example = "1024") + @ExcelProperty("创建人") + private String creator; + @Schema(description = "创建人名字", example = "芋道源码") + @ExcelProperty("创建人名字") + private String creatorName; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanSaveReqVO.java new file mode 100644 index 000000000..470734c65 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanSaveReqVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - CRM 回款计划新增/修改 Request VO") +@Data +public class CrmReceivablePlanSaveReqVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long id; + + @Schema(description = "客户编号", hidden = true, example = "2") + private Long customerId; // 该字段不通过前端传递,而是 contractId 查询出来设置进去 + + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "合同编号不能为空") + private Long contractId; + + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @NotNull(message = "负责人编号不能为空") + private Long ownerUserId; + + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") + @NotNull(message = "计划回款日期不能为空") + private LocalDateTime returnTime; + + @Schema(description = "回款方式", example = "1") + private Integer returnType; + + @Schema(description = "计划回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") + @NotNull(message = "计划回款金额不能为空") + private BigDecimal price; + + @Schema(description = "提前几天提醒", example = "1") + private Integer remindDays; + + @Schema(description = "备注", example = "备注") + private String remark; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanUpdateReqVO.java deleted file mode 100644 index 2e83a1cbb..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/plan/CrmReceivablePlanUpdateReqVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -import jakarta.validation.constraints.*; - -@Schema(description = "管理后台 - CRM 回款计划更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivablePlanUpdateReqVO extends CrmReceivablePlanBaseVO { - - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25153") - @NotNull(message = "ID不能为空") - private Long id; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableBaseVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableBaseVO.java deleted file mode 100644 index c32bc9ad5..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableBaseVO.java +++ /dev/null @@ -1,61 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; - -import cn.iocoder.yudao.framework.common.validation.InEnum; -import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -/** - * 回款 Base VO,提供给添加、修改、详细的子 VO 使用 - * 如果子 VO 存在差异的字段,请不要添加到这里,影响 Swagger 文档生成 - */ -@Data -public class CrmReceivableBaseVO { - - @Schema(description = "回款编号",requiredMode = Schema.RequiredMode.REQUIRED, example = "31177") - private String no; - - // TODO @liuhongfeng:回款计划编号 - @Schema(description = "回款计划", example = "31177") - private Long planId; - - // TODO @liuhongfeng:客户编号 - @Schema(description = "客户名称", example = "4963") - private Long customerId; - - // TODO @liuhongfeng:客户编号 - @Schema(description = "合同名称", example = "30305") - private Long contractId; - - // TODO @liuhongfeng:这个字段,应该不是前端传递的噢,而是后端自己生成的 - @Schema(description = "审批状态", example = "1") - @InEnum(CrmAuditStatusEnum.class) - private Integer checkStatus; - - @Schema(description = "回款日期") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime returnTime; - - @Schema(description = "回款方式", example = "2") - private Integer returnType; - - @Schema(description = "回款金额,单位:分", example = "31859") - private Integer price; - - // TODO @liuhongfeng:负责人编号 - @Schema(description = "负责人", example = "22202") - private Long ownerUserId; - - @Schema(description = "显示顺序") - private Integer sort; - - @Schema(description = "备注", example = "备注") - private String remark; - - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableCreateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableCreateReqVO.java deleted file mode 100644 index 4471b780a..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableCreateReqVO.java +++ /dev/null @@ -1,12 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; - -import lombok.*; -import io.swagger.v3.oas.annotations.media.Schema; - -@Schema(description = "管理后台 - CRM 回款创建 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivableCreateReqVO extends CrmReceivableBaseVO { - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java index e1fe83087..415816c5d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivablePageReqVO.java @@ -24,6 +24,9 @@ public class CrmReceivablePageReqVO extends PageParam { @Schema(description = "客户编号", example = "4963") private Long customerId; + @Schema(description = "合同编号", example = "4963") + private Long contractId; + @Schema(description = "场景类型", example = "1") @InEnum(CrmSceneTypeEnum.class) private Integer sceneType; // 场景类型,为 null 时则表示全部 diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java index 7c536bd51..a9fcb1b8b 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableRespVO.java @@ -1,37 +1,74 @@ package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractRespVO; +import com.alibaba.excel.annotation.ExcelProperty; import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; +import lombok.Data; + +import java.math.BigDecimal; import java.time.LocalDateTime; // TODO 芋艿:导出的 VO,可以考虑使用 @Excel 注解,实现导出功能 @Schema(description = "管理后台 - CRM 回款 Response VO") @Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivableRespVO extends CrmReceivableBaseVO { +public class CrmReceivableRespVO { - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25787") + @Schema(description = "编号", example = "25787") private Long id; - @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) - private LocalDateTime createTime; + @Schema(description = "回款编号", example = "31177") + private String no; + @Schema(description = "回款计划编号", example = "1024") + private Long planId; + + @Schema(description = "回款方式", example = "2") + private Integer returnType; + + @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") + private BigDecimal price; + + @Schema(description = "计划回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") + private LocalDateTime returnTime; + + @Schema(description = "客户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long customerId; @Schema(description = "客户名字", requiredMode = Schema.RequiredMode.REQUIRED, example = "test") private String customerName; + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + private Long contractId; + @Schema(description = "合同信息") + private CrmContractRespVO contract; + + @Schema(description = "负责人的用户编号", example = "25682") + @ExcelProperty("负责人的用户编号") + private Long ownerUserId; + @Schema(description = "负责人名字", example = "25682") + @ExcelProperty("负责人名字") + private String ownerUserName; + @Schema(description = "负责人部门") + @ExcelProperty("负责人部门") + private String ownerUserDeptName; + + @Schema(description = "工作流编号", example = "1043") + @ExcelProperty("工作流编号") + private String processInstanceId; + @Schema(description = "审批状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") private Integer auditStatus; - @Schema(description = "合同编号", example = "Q110") - private String contractNo; + @Schema(description = "备注", example = "备注") + private String remark; - @Schema(description = "负责人", example = "test") - private String ownerUserName; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + + @Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime updateTime; @Schema(description = "创建人", example = "25682") private String creator; - @Schema(description = "创建人名字", example = "test") private String creatorName; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableSaveReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableSaveReqVO.java new file mode 100644 index 000000000..8ab85daeb --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableSaveReqVO.java @@ -0,0 +1,58 @@ +package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.crm.enums.receivable.CrmReceivableReturnTypeEnum; +import cn.iocoder.yudao.module.crm.framework.operatelog.core.*; +import com.mzt.logapi.starter.annotation.DiffLogField; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.math.BigDecimal; +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - CRM 回款新增/修改 Request VO") +@Data +public class CrmReceivableSaveReqVO { + + @Schema(description = "编号", example = "25787") + private Long id; + + @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @DiffLogField(name = "负责人", function = SysAdminUserParseFunction.NAME) + @NotNull(message = "负责人编号不能为空") + private Long ownerUserId; + + @Schema(description = "客户编号", example = "2") + @DiffLogField(name = "客户", function = CrmCustomerParseFunction.NAME) + private Long customerId; // 该字段不通过前端传递,而是 contractId 查询出来设置进去 + + @Schema(description = "合同编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2") + @DiffLogField(name = "合同", function = CrmContractParseFunction.NAME) + @NotNull(message = "合同编号不能为空") + private Long contractId; + + @Schema(description = "回款计划编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @DiffLogField(name = "合同", function = CrmReceivablePlanParseFunction.NAME) + private Long planId; + + @Schema(description = "回款方式", example = "2") + @DiffLogField(name = "回款方式", function = CrmReceivableReturnTypeParseFunction.NAME) + @InEnum(CrmReceivableReturnTypeEnum.class) + private Integer returnType; + + @Schema(description = "回款金额", requiredMode = Schema.RequiredMode.REQUIRED, example = "9000") + @DiffLogField(name = "回款金额") + @NotNull(message = "回款金额不能为空") + private BigDecimal price; + + @Schema(description = "回款日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02") + @NotNull(message = "回款日期不能为空") + @DiffLogField(name = "回款日期") + private LocalDateTime returnTime; + + @Schema(description = "备注", example = "备注") + @DiffLogField(name = "备注") + private String remark; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableUpdateReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableUpdateReqVO.java deleted file mode 100644 index 0f63978c8..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/receivable/vo/receivable/CrmReceivableUpdateReqVO.java +++ /dev/null @@ -1,18 +0,0 @@ -package cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.*; - -import jakarta.validation.constraints.*; - -@Schema(description = "管理后台 - CRM 回款更新 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class CrmReceivableUpdateReqVO extends CrmReceivableBaseVO { - - @Schema(description = "ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "25787") - @NotNull(message = "ID不能为空") - private Long id; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.http b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.http new file mode 100644 index 000000000..e878ba1a9 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.http @@ -0,0 +1,9 @@ +### 合同金额排行榜 +GET {{baseUrl}}/crm/statistics-rank/get-contract-price-rank?deptId=100×[0]=2022-12-12 00:00:00×[1]=2024-12-12 23:59:59 +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} + +### 回款金额排行榜 +GET {{baseUrl}}/crm/statistics-rank/get-receivable-price-rank?deptId=100×[0]=2022-12-12 00:00:00×[1]=2024-12-12 23:59:59 +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.java similarity index 51% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.java index 21463aed0..e4cf61f7a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/CrmBiRankController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsRankController.java @@ -1,9 +1,9 @@ -package cn.iocoder.yudao.module.crm.controller.admin.bi; +package cn.iocoder.yudao.module.crm.controller.admin.statistics; import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO; -import cn.iocoder.yudao.module.crm.service.bi.CrmBiRankingService; +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO; +import cn.iocoder.yudao.module.crm.service.statistics.CrmStatisticsRankingService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; @@ -19,68 +19,68 @@ import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; -@Tag(name = "管理后台 - CRM BI 排行榜") +@Tag(name = "管理后台 - CRM 排行榜统计") @RestController -@RequestMapping("/crm/bi-rank") +@RequestMapping("/crm/statistics-rank") @Validated -public class CrmBiRankController { +public class CrmStatisticsRankController { @Resource - private CrmBiRankingService rankingService; + private CrmStatisticsRankingService rankingService; @GetMapping("/get-contract-price-rank") @Operation(summary = "获得合同金额排行榜") - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") - public CommonResult> getContractPriceRank(@Valid CrmBiRankReqVO rankingReqVO) { + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") + public CommonResult> getContractPriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) { return success(rankingService.getContractPriceRank(rankingReqVO)); } @GetMapping("/get-receivable-price-rank") @Operation(summary = "获得回款金额排行榜") - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") - public CommonResult> getReceivablePriceRank(@Valid CrmBiRankReqVO rankingReqVO) { + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") + public CommonResult> getReceivablePriceRank(@Valid CrmStatisticsRankReqVO rankingReqVO) { return success(rankingService.getReceivablePriceRank(rankingReqVO)); } @GetMapping("/get-contract-count-rank") @Operation(summary = "获得签约合同数量排行榜") - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") - public CommonResult> getContractCountRank(@Valid CrmBiRankReqVO rankingReqVO) { + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") + public CommonResult> getContractCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) { return success(rankingService.getContractCountRank(rankingReqVO)); } @GetMapping("/get-product-sales-rank") @Operation(summary = "获得产品销量排行榜") - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") - public CommonResult> getProductSalesRank(@Valid CrmBiRankReqVO rankingReqVO) { + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") + public CommonResult> getProductSalesRank(@Valid CrmStatisticsRankReqVO rankingReqVO) { return success(rankingService.getProductSalesRank(rankingReqVO)); } @GetMapping("/get-customer-count-rank") @Operation(summary = "获得新增客户数排行榜") - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") - public CommonResult> getCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) { + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") + public CommonResult> getCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) { return success(rankingService.getCustomerCountRank(rankingReqVO)); } @GetMapping("/get-contacts-count-rank") @Operation(summary = "获得新增联系人数排行榜") - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") - public CommonResult> getContactsCountRank(@Valid CrmBiRankReqVO rankingReqVO) { + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") + public CommonResult> getContactsCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) { return success(rankingService.getContactsCountRank(rankingReqVO)); } @GetMapping("/get-follow-count-rank") @Operation(summary = "获得跟进次数排行榜") - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") - public CommonResult> getFollowCountRank(@Valid CrmBiRankReqVO rankingReqVO) { + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") + public CommonResult> getFollowCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) { return success(rankingService.getFollowCountRank(rankingReqVO)); } @GetMapping("/get-follow-customer-count-rank") @Operation(summary = "获得跟进客户数排行榜") - @PreAuthorize("@ss.hasPermission('crm:bi-rank:query')") - public CommonResult> getFollowCustomerCountRank(@Valid CrmBiRankReqVO rankingReqVO) { + @PreAuthorize("@ss.hasPermission('crm:statistics-rank:query')") + public CommonResult> getFollowCustomerCountRank(@Valid CrmStatisticsRankReqVO rankingReqVO) { return success(rankingService.getFollowCustomerCountRank(rankingReqVO)); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRanKRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRanKRespVO.java similarity index 83% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRanKRespVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRanKRespVO.java index 404ee3352..d5c865fd3 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRanKRespVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRanKRespVO.java @@ -1,12 +1,12 @@ -package cn.iocoder.yudao.module.crm.controller.admin.bi.vo; +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; -@Schema(description = "管理后台 - CRM BI 排行榜 Response VO") +@Schema(description = "管理后台 - CRM BI 排行榜统计 Response VO") @Data -public class CrmBiRanKRespVO { +public class CrmStatisticsRanKRespVO { @Schema(description = "负责人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private Long ownerUserId; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRankReqVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRankReqVO.java similarity index 86% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRankReqVO.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRankReqVO.java index 6d36f6d6f..487921957 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/bi/vo/CrmBiRankReqVO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/CrmStatisticsRankReqVO.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.controller.admin.bi.vo; +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; @@ -11,9 +11,9 @@ import java.util.List; import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; -@Schema(description = "管理后台 - CRM BI 排行榜 Request VO") +@Schema(description = "管理后台 - CRM 排行榜统计 Request VO") @Data -public class CrmBiRankReqVO { +public class CrmStatisticsRankReqVO { @Schema(description = "部门 id", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "部门 id 不能为空") diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java deleted file mode 100644 index d7f990043..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessConvert.java +++ /dev/null @@ -1,57 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.business; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; - -/** - * 商机 Convert - * - * @author ljlleo - */ -@Mapper -public interface CrmBusinessConvert { - - CrmBusinessConvert INSTANCE = Mappers.getMapper(CrmBusinessConvert.class); - - @Mapping(target = "bizId", source = "reqVO.id") - CrmPermissionTransferReqBO convert(CrmBusinessTransferReqVO reqVO, Long userId); - - default PageResult convertPage(PageResult pageResult, List customerList, - List statusTypeList, List statusList) { - PageResult voPageResult = BeanUtils.toBean(pageResult, CrmBusinessRespVO.class); - // 拼接关联字段 - Map customerMap = convertMap(customerList, CrmCustomerDO::getId, CrmCustomerDO::getName); - Map statusTypeMap = convertMap(statusTypeList, CrmBusinessStatusTypeDO::getId, CrmBusinessStatusTypeDO::getName); - Map statusMap = convertMap(statusList, CrmBusinessStatusDO::getId, CrmBusinessStatusDO::getName); - voPageResult.getList().forEach(type -> type - .setCustomerName(customerMap.get(type.getCustomerId())) - .setStatusTypeName(statusTypeMap.get(type.getStatusTypeId())) - .setStatusName(statusMap.get(type.getStatusId()))); - return voPageResult; - } - - @Mapping(target = "id", source = "reqBO.bizId") - CrmBusinessDO convert(CrmUpdateFollowUpReqBO reqBO); - - default List convertList(List list) { - return CollectionUtils.convertList(list, INSTANCE::convert); - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java deleted file mode 100644 index 52186e3d9..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusConvert.java +++ /dev/null @@ -1,25 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.business; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusRespVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; - -/** - * 商机状态 Convert - * - * @author ljlleo - */ -@Mapper -public interface CrmBusinessStatusConvert { - - CrmBusinessStatusConvert INSTANCE = Mappers.getMapper(CrmBusinessStatusConvert.class); - - List convertList(List list); - - PageResult convertPage(PageResult page); - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java deleted file mode 100644 index 4876fb537..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/business/CrmBusinessStatusTypeConvert.java +++ /dev/null @@ -1,44 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.business; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeRespVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; -import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; - -// TODO @lzxhqs:看看是不是用 BeanUtils 替代了 -/** - * 商机状态类型 Convert - * - * @author ljlleo - */ -@Mapper -public interface CrmBusinessStatusTypeConvert { - - CrmBusinessStatusTypeConvert INSTANCE = Mappers.getMapper(CrmBusinessStatusTypeConvert.class); - - CrmBusinessStatusTypeRespVO convert(CrmBusinessStatusTypeDO bean); - - PageResult convertPage(PageResult page); - - default PageResult convertPage(PageResult page, List deptList) { - PageResult pageResult = convertPage(page); - // 拼接关联字段 - Map deptMap = convertMap(deptList, DeptRespDTO::getId, DeptRespDTO::getName); - pageResult.getList().forEach(type -> type.setDeptNames(convertList(type.getDeptIds(), deptMap::get))); - return pageResult; - } - - default CrmBusinessStatusTypeRespVO convert(CrmBusinessStatusTypeDO bean, List statusList) { - return convert(bean).setStatusList(statusList); - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java deleted file mode 100644 index 39e607bcb..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/clue/CrmClueConvert.java +++ /dev/null @@ -1,22 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.clue; - -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO; -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; - -/** - * 线索 Convert - * - * @author Wanwan - */ -@Mapper -public interface CrmClueConvert { - - CrmClueConvert INSTANCE = Mappers.getMapper(CrmClueConvert.class); - - @Mapping(target = "bizId", source = "reqVO.id") - CrmPermissionTransferReqBO convert(CrmClueTransferReqVO reqVO, Long userId); - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java deleted file mode 100644 index 5fd2afa67..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contact/CrmContactConvert.java +++ /dev/null @@ -1,77 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.contact; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; - -/** - * CRM 联系人 Convert - * - * @author 芋道源码 - */ -@Mapper -public interface CrmContactConvert { - - CrmContactConvert INSTANCE = Mappers.getMapper(CrmContactConvert.class); - - CrmContactRespVO convert(CrmContactDO bean); - - @Mapping(target = "bizId", source = "reqVO.id") - CrmPermissionTransferReqBO convert(CrmContactTransferReqVO reqVO, Long userId); - - default PageResult convertPage(PageResult pageResult, Map userMap, - List customerList, List parentContactList) { - PageResult voPageResult = BeanUtils.toBean(pageResult, CrmContactRespVO.class); - // 拼接关联字段 - Map parentContactMap = convertMap(parentContactList, CrmContactDO::getId); - Map customerMap = convertMap(customerList, CrmCustomerDO::getId); - voPageResult.getList().forEach(item -> { - setUserInfo(item, userMap); - findAndThen(customerMap, item.getCustomerId(), customer -> item.setCustomerName(customer.getName())); - findAndThen(parentContactMap, item.getParentId(), contactDO -> item.setParentName(contactDO.getName())); - }); - return voPageResult; - } - - default CrmContactRespVO convert(CrmContactDO contactDO, Map userMap, - List customerList, List parentContactList) { - CrmContactRespVO contactVO = convert(contactDO); - setUserInfo(contactVO, userMap); - Map customerMap = CollectionUtils.convertMap(customerList, CrmCustomerDO::getId); - Map contactMap = CollectionUtils.convertMap(parentContactList, CrmContactDO::getId); - findAndThen(customerMap, contactDO.getCustomerId(), customer -> contactVO.setCustomerName(customer.getName())); - findAndThen(contactMap, contactDO.getParentId(), contact -> contactVO.setParentName(contact.getName())); - return contactVO; - } - - static void setUserInfo(CrmContactRespVO contactRespVO, Map userMap) { - contactRespVO.setAreaName(AreaUtils.format(contactRespVO.getAreaId())); - findAndThen(userMap, contactRespVO.getOwnerUserId(), user -> contactRespVO.setOwnerUserName(user.getNickname())); - findAndThen(userMap, Long.parseLong(contactRespVO.getCreator()), user -> contactRespVO.setCreatorName(user.getNickname())); - } - - @Mapping(target = "id", source = "reqBO.bizId") - CrmContactDO convert(CrmUpdateFollowUpReqBO reqBO); - - default List convertList(List list) { - return CollectionUtils.convertList(list, INSTANCE::convert); - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java deleted file mode 100644 index 0d2e49934..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/contract/CrmContractConvert.java +++ /dev/null @@ -1,70 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.contract; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; - -/** - * 合同 Convert - * - * @author dhb52 - */ -@Mapper -public interface CrmContractConvert { - - CrmContractConvert INSTANCE = Mappers.getMapper(CrmContractConvert.class); - - @Mapping(target = "bizId", source = "reqVO.id") - CrmPermissionTransferReqBO convert(CrmContractTransferReqVO reqVO, Long userId); - - default List convertList(List contractList, Map userMap, - List customerList, Map contactMap, - Map businessMap, Map contractProductMap, - List productList) { - List respVOList = BeanUtils.toBean(contractList, CrmContractRespVO.class); - // 拼接关联字段 - Map customerMap = convertMap(customerList, CrmCustomerDO::getId); - respVOList.forEach(contract -> { - findAndThen(userMap, contract.getOwnerUserId(), user -> contract.setOwnerUserName(user.getNickname())); - findAndThen(userMap, Long.parseLong(contract.getCreator()), user -> contract.setCreatorName(user.getNickname())); - findAndThen(userMap, contract.getSignUserId(), user -> contract.setSignUserName(user.getNickname())); - findAndThen(customerMap, contract.getCustomerId(), customer -> contract.setCustomerName(customer.getName())); - findAndThen(contactMap, contract.getContactId(), contact -> contract.setContactName(contact.getName())); - findAndThen(businessMap, contract.getBusinessId(), business -> contract.setBusinessName(business.getName())); - }); - if (CollUtil.isNotEmpty(respVOList) && respVOList.size() == 1) { - setContractRespVOProductItems(respVOList.get(0), contractProductMap, productList); - } - return respVOList; - } - - default void setContractRespVOProductItems(CrmContractRespVO respVO, Map contractProductMap, - List productList) { - respVO.setProductItems(CollectionUtils.convertList(productList, product -> { - CrmContractRespVO.CrmContractProductItemRespVO productItemRespVO = BeanUtils.toBean(product, CrmContractRespVO.CrmContractProductItemRespVO.class); - findAndThen(contractProductMap, product.getId(), contractProduct -> - productItemRespVO.setCount(contractProduct.getCount()).setDiscountPercent(contractProduct.getDiscountPercent())); - return productItemRespVO; - })); - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java deleted file mode 100644 index 6a6642968..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerConvert.java +++ /dev/null @@ -1,66 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.customer; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; -import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.mapstruct.Mapper; -import org.mapstruct.Mapping; -import org.mapstruct.factory.Mappers; - -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; - -/** - * 客户 Convert - * - * @author Wanwan - */ -@Mapper -public interface CrmCustomerConvert { - - CrmCustomerConvert INSTANCE = Mappers.getMapper(CrmCustomerConvert.class); - - default CrmCustomerRespVO convert(CrmCustomerDO customer, Map userMap, - Map deptMap) { - CrmCustomerRespVO customerResp = BeanUtils.toBean(customer, CrmCustomerRespVO.class); - setUserInfo(customerResp, userMap, deptMap); - return customerResp; - } - - default PageResult convertPage(PageResult pageResult, Map userMap, - Map deptMap, Map poolDayMap) { - PageResult result = BeanUtils.toBean(pageResult, CrmCustomerRespVO.class); - result.getList().forEach(item -> { - setUserInfo(item, userMap, deptMap); - findAndThen(poolDayMap, item.getId(), item::setPoolDay); - }); - return result; - } - - /** - * 设置用户信息 - * - * @param customer CRM 客户 Response VO - * @param userMap 用户信息 map - * @param deptMap 用户部门信息 map - */ - static void setUserInfo(CrmCustomerRespVO customer, Map userMap, Map deptMap) { - customer.setAreaName(AreaUtils.format(customer.getAreaId())); - findAndThen(userMap, customer.getOwnerUserId(), user -> { - customer.setOwnerUserName(user.getNickname()); - findAndThen(deptMap, user.getDeptId(), dept -> customer.setOwnerUserDeptName(dept.getName())); - }); - findAndThen(userMap, Long.parseLong(customer.getCreator()), user -> customer.setCreatorName(user.getNickname())); - } - - @Mapping(target = "bizId", source = "reqVO.id") - CrmPermissionTransferReqBO convert(CrmCustomerTransferReqVO reqVO, Long userId); - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerLimitConfigConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerLimitConfigConvert.java deleted file mode 100644 index 81550b378..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/customer/CrmCustomerLimitConfigConvert.java +++ /dev/null @@ -1,42 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.customer; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.limitconfig.CrmCustomerLimitConfigRespVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; -import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; - -/** - * 客户限制配置 Convert - * - * @author Wanwan - */ -@Mapper -public interface CrmCustomerLimitConfigConvert { - - CrmCustomerLimitConfigConvert INSTANCE = Mappers.getMapper(CrmCustomerLimitConfigConvert.class); - - default PageResult convertPage( - PageResult pageResult, - Map userMap, Map deptMap) { - List list = CollectionUtils.convertList(pageResult.getList(), - limitConfig -> convert(limitConfig, userMap, deptMap)); - return new PageResult<>(list, pageResult.getTotal()); - } - - default CrmCustomerLimitConfigRespVO convert(CrmCustomerLimitConfigDO limitConfig, - Map userMap, Map deptMap) { - CrmCustomerLimitConfigRespVO limitConfigVO = BeanUtils.toBean(limitConfig, CrmCustomerLimitConfigRespVO.class); - limitConfigVO.setUsers(CollectionUtils.convertList(limitConfigVO.getUserIds(), userMap::get)); - limitConfigVO.setDepts(CollectionUtils.convertList(limitConfigVO.getDeptIds(), deptMap::get)); - return limitConfigVO; - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java deleted file mode 100644 index f51544cae..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java +++ /dev/null @@ -1,56 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.permission; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; -import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; -import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import com.google.common.collect.Multimaps; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; - -/** - * Crm 数据权限 Convert - * - * @author HUIHUI - */ -@Mapper -public interface CrmPermissionConvert { - - CrmPermissionConvert INSTANCE = Mappers.getMapper(CrmPermissionConvert.class); - - default List convert(List permissions, List userList, - Map deptMap, Map postMap) { - Map userMap = CollectionUtils.convertMap(userList, AdminUserRespDTO::getId); - return CollectionUtils.convertList(BeanUtils.toBean(permissions, CrmPermissionRespVO.class), item -> { - findAndThen(userMap, item.getUserId(), user -> { - item.setNickname(user.getNickname()); - findAndThen(deptMap, user.getDeptId(), deptRespDTO -> item.setDeptName(deptRespDTO.getName())); - if (CollUtil.isEmpty(user.getPostIds())) { - item.setPostNames(Collections.emptySet()); - return; - } - List postList = MapUtils.getList(Multimaps.forMap(postMap), user.getPostIds()); - item.setPostNames(CollectionUtils.convertSet(postList, PostRespDTO::getName)); - }); - return item; - }); - } - - default List convertList(CrmPermissionUpdateReqVO updateReqVO) { - return CollectionUtils.convertList(updateReqVO.getIds(), - id -> new CrmPermissionDO().setId(id).setLevel(updateReqVO.getLevel())); - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/product/CrmProductConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/product/CrmProductConvert.java deleted file mode 100644 index 2165a208f..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/product/CrmProductConvert.java +++ /dev/null @@ -1,46 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.product; - -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductRespVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; - -/** - * 产品 Convert - * - * @author ZanGe丶 - */ -@Mapper -public interface CrmProductConvert { - - CrmProductConvert INSTANCE = Mappers.getMapper(CrmProductConvert.class); - - default List convertList(List list, - Map userMap, - List categoryList) { - Map categoryMap = convertMap(categoryList, CrmProductCategoryDO::getId); - return CollectionUtils.convertList(list, - product -> convert(product, userMap, categoryMap.get(product.getCategoryId()))); - } - - default CrmProductRespVO convert(CrmProductDO product, - Map userMap, CrmProductCategoryDO category) { - CrmProductRespVO productVO = BeanUtils.toBean(product, CrmProductRespVO.class); - Optional.ofNullable(category).ifPresent(c -> productVO.setCategoryName(c.getName())); - MapUtils.findAndThen(userMap, productVO.getOwnerUserId(), user -> productVO.setOwnerUserName(user.getNickname())); - MapUtils.findAndThen(userMap, Long.valueOf(productVO.getCreator()), user -> productVO.setCreatorName(user.getNickname())); - return productVO; - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java deleted file mode 100644 index 3b0c23aae..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivableConvert.java +++ /dev/null @@ -1,52 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.receivable; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; - -/** - * 回款 Convert - * - * @author 赤焰 - */ -@Mapper -public interface CrmReceivableConvert { - - CrmReceivableConvert INSTANCE = Mappers.getMapper(CrmReceivableConvert.class); - - CrmReceivableDO convert(CrmReceivableCreateReqVO bean); - - CrmReceivableDO convert(CrmReceivableUpdateReqVO bean); - - CrmReceivableRespVO convert(CrmReceivableDO bean); - - default PageResult convertPage(PageResult pageResult, Map userMap, - List customerList, List contractList) { - PageResult voPageResult = BeanUtils.toBean(pageResult, CrmReceivableRespVO.class); - // 拼接关联字段 - Map customerMap = convertMap(customerList, CrmCustomerDO::getId); - Map contractMap = convertMap(contractList, CrmContractDO::getId); - voPageResult.getList().forEach(receivable -> { - findAndThen(userMap, receivable.getOwnerUserId(), user -> receivable.setOwnerUserName(user.getNickname())); - findAndThen(userMap, Long.parseLong(receivable.getCreator()), user -> receivable.setCreatorName(user.getNickname())); - findAndThen(customerMap, receivable.getCustomerId(), customer -> receivable.setCustomerName(customer.getName())); - findAndThen(contractMap, receivable.getContractId(), contract -> receivable.setContractNo(contract.getNo())); - }); - return voPageResult; - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java deleted file mode 100644 index 9b6bb3e82..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/receivable/CrmReceivablePlanConvert.java +++ /dev/null @@ -1,56 +0,0 @@ -package cn.iocoder.yudao.module.crm.convert.receivable; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; -import org.mapstruct.Mapper; -import org.mapstruct.factory.Mappers; - -import java.util.List; -import java.util.Map; - -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; -import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen; - -/** - * 回款计划 Convert - * - * @author 芋道源码 - */ -@Mapper -public interface CrmReceivablePlanConvert { - - CrmReceivablePlanConvert INSTANCE = Mappers.getMapper(CrmReceivablePlanConvert.class); - - CrmReceivablePlanDO convert(CrmReceivablePlanCreateReqVO bean); - - CrmReceivablePlanDO convert(CrmReceivablePlanUpdateReqVO bean); - - CrmReceivablePlanRespVO convert(CrmReceivablePlanDO bean); - - default PageResult convertPage(PageResult pageResult, Map userMap, - List customerList, List contractList, - List receivableList) { - PageResult voPageResult = BeanUtils.toBean(pageResult, CrmReceivablePlanRespVO.class); - // 拼接关联字段 - Map customerMap = convertMap(customerList, CrmCustomerDO::getId); - Map contractMap = convertMap(contractList, CrmContractDO::getId); - Map receivableMap = convertMap(receivableList, CrmReceivableDO::getId); - voPageResult.getList().forEach(receivablePlan -> { - findAndThen(userMap, receivablePlan.getOwnerUserId(), user -> receivablePlan.setOwnerUserName(user.getNickname())); - findAndThen(userMap, Long.parseLong(receivablePlan.getCreator()), user -> receivablePlan.setCreatorName(user.getNickname())); - findAndThen(customerMap, receivablePlan.getCustomerId(), customer -> receivablePlan.setCustomerName(customer.getName())); - findAndThen(contractMap, receivablePlan.getContractId(), contract -> receivablePlan.setContractNo(contract.getNo())); - findAndThen(receivableMap, receivablePlan.getReceivableId(), receivable -> receivablePlan.setReturnType(receivable.getReturnType())); - }); - return voPageResult; - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java index 3a543712b..a30ece21f 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessDO.java @@ -2,16 +2,17 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.business; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.enums.business.CrmBizEndStatus; +import cn.iocoder.yudao.module.crm.enums.business.CrmBusinessEndStatusEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; +import java.math.BigDecimal; import java.time.LocalDateTime; /** - * 商机 DO + * CRM 商机 DO * * @author ljlleo */ @@ -26,7 +27,7 @@ import java.time.LocalDateTime; public class CrmBusinessDO extends BaseDO { /** - * 主键 + * 编号 */ @TableId private Long id; @@ -35,7 +36,34 @@ public class CrmBusinessDO extends BaseDO { */ private String name; /** - * 商机状态类型编号 + * 客户编号 + * + * 关联 {@link CrmCustomerDO#getId()} + */ + private Long customerId; + + /** + * 跟进状态 + */ + private Boolean followUpStatus; + /** + * 最后跟进时间 + */ + private LocalDateTime contactLastTime; + /** + * 下次联系时间 + */ + private LocalDateTime contactNextTime; + + /** + * 负责人的用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + + /** + * 商机状态组编号 * * 关联 {@link CrmBusinessStatusTypeDO#getId()} */ @@ -46,63 +74,38 @@ public class CrmBusinessDO extends BaseDO { * 关联 {@link CrmBusinessStatusDO#getId()} */ private Long statusId; - /** - * 下次联系时间 - */ - private LocalDateTime contactNextTime; - /** - * 客户编号 - * - * TODO @ljileo:这个字段,后续要写下关联的实体哈 - * 关联 {@link CrmCustomerDO#getId()} - */ - private Long customerId; - /** - * 预计成交日期 - */ - private LocalDateTime dealTime; - /** - * 商机金额 - * - */ - private Integer price; - /** - * 整单折扣 - * - */ - private Integer discountPercent; - /** - * 产品总金额,单位:分 - */ - private Integer productPrice; - /** - * 备注 - */ - private String remark; /** * 结束状态 * - * 枚举 {@link CrmBizEndStatus} + * 枚举 {@link CrmBusinessEndStatusEnum} */ private Integer endStatus; /** * 结束时的备注 */ private String endRemark; - /** - * 最后跟进时间 - */ - private LocalDateTime contactLastTime; - /** - * 跟进状态 - */ - private Boolean followUpStatus; /** - * 负责人的用户编号 - * - * 关联 AdminUserDO 的 id 字段 + * 预计成交日期 */ - private Long ownerUserId; + private LocalDateTime dealTime; + /** + * 产品总金额,单位:元 + * + * productPrice = ∑({@link CrmBusinessProductDO#getTotalPrice()}) + */ + private BigDecimal totalProductPrice; + /** + * 整单折扣,百分比 + */ + private BigDecimal discountPercent; + /** + * 商机总金额,单位:元 + */ + private BigDecimal totalPrice; + /** + * 备注 + */ + private String remark; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java index 79d6a2a7b..2f66fc34d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessProductDO.java @@ -7,8 +7,12 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; +import java.math.BigDecimal; + /** - * 商机产品关联表 DO + * CRM 商机产品关联表 DO + * + * CrmBusinessDO : CrmBusinessProductDO = 1 : N * * @author lzxhqs */ @@ -40,24 +44,24 @@ public class CrmBusinessProductDO extends BaseDO { */ private Long productId; /** - * 产品单价 + * 产品单价,单位:元 + * + * 冗余 {@link CrmProductDO#getPrice()} */ - private Integer price; + private BigDecimal productPrice; /** - * 销售价格, 单位:分 + * 商机价格, 单位:元 */ - private Integer salesPrice; + private BigDecimal businessPrice; /** * 数量 */ - private Integer count; + private BigDecimal count; /** - * 折扣 + * 总计价格,单位:元 + * + * totalPrice = businessPrice * count */ - private Integer discountPercent; - /** - * 总计价格(折扣后价格) - */ - private Integer totalPrice; + private BigDecimal totalPrice; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java index 17ce4f88c..4ec8bbe4a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusDO.java @@ -6,7 +6,9 @@ import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; /** - * 商机状态 DO + * CRM 商机状态 DO + * + * 注意,它是个配置表 * * @author ljlleo */ diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java index aa9f86251..95b7a1f26 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/business/CrmBusinessStatusTypeDO.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.business; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.mybatis.core.type.LongListTypeHandler; import com.baomidou.mybatisplus.annotation.KeySequence; @@ -12,7 +11,9 @@ import lombok.*; import java.util.List; /** - * 商机状态类型 DO + * CRM 商机状态组 DO + * + * 注意,它是个配置表 * * @author ljlleo */ @@ -35,17 +36,11 @@ public class CrmBusinessStatusTypeDO extends BaseDO { * 状态类型名 */ private String name; + /** * 使用的部门编号 */ @TableField(typeHandler = LongListTypeHandler.class) private List deptIds; - /** - * 开启状态 - * - * TODO 改成 Integer,关联 CommonStatus - * 枚举 {@link CommonStatusEnum} - */ - private Boolean status; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java index 5ea1dea28..3af6feec4 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/clue/CrmClueDO.java @@ -10,9 +10,8 @@ import lombok.*; import java.time.LocalDateTime; -// TODO 芋艿:字段的顺序,需要整理下; /** - * 线索 DO + * CRM 线索 DO * * @author Wanwan */ @@ -32,71 +31,55 @@ public class CrmClueDO extends BaseDO { @TableId private Long id; /** - * 转化状态 + * 线索名称 */ - private Boolean transformStatus; + private String name; + /** * 跟进状态 */ private Boolean followUpStatus; /** - * 线索名称 + * 最后跟进时间 */ - private String name; + private LocalDateTime contactLastTime; /** - * 客户 id - * - * 关联 {@link CrmCustomerDO#getId()} + * 最后跟进内容 */ - private Long customerId; + private String contactLastContent; /** * 下次联系时间 */ private LocalDateTime contactNextTime; + /** - * 电话 + * 负责人的用户编号 + * + * 关联 AdminUserDO 的 id 字段 */ - private String telephone; + private Long ownerUserId; + + /** + * 转化状态 + * + * true 表示已转换,会更新 {@link #customerId} 字段 + */ + private Boolean transformStatus; + /** + * 客户编号 + * + * 关联 {@link CrmCustomerDO#getId()} + */ + private Long customerId; + /** * 手机号 */ private String mobile; /** - * 地址 + * 电话 */ - private String address; - /** - * 最后跟进时间 TODO 添加跟进记录时更新该值 - */ - private LocalDateTime contactLastTime; - /** - * 备注 - */ - private String remark; - /** - * 负责人的用户编号 - * 关联 AdminUserDO 的 id 字段 - */ - private Long ownerUserId; - /** - * 所属行业 - * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_INDUSTRY} - */ - private Integer industryId; - /** - * 客户等级 - * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_LEVEL} - */ - private Integer level; - /** - * 客户来源 - * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_SOURCE} - */ - private Integer source; - /** - * 网址 - */ - private String website; + private String telephone; /** * QQ */ @@ -110,7 +93,36 @@ public class CrmClueDO extends BaseDO { */ private String email; /** - * 客户描述 + * 所在地 + * + * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段 */ - private String description; + private Integer areaId; + /** + * 详细地址 + */ + private String detailAddress; + /** + * 所属行业 + * + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_INDUSTRY} + */ + private Integer industryId; + /** + * 客户等级 + * + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_LEVEL} + */ + private Integer level; + /** + * 客户来源 + * + * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_SOURCE} + */ + private Integer source; + /** + * 备注 + */ + private String remark; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java index c2dec247d..5a891eb9c 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contact/CrmContactDO.java @@ -29,77 +29,16 @@ public class CrmContactDO extends BaseDO { */ @TableId private Long id; + /** + * 联系人姓名 + */ + private String name; /** * 客户编号 * * 关联 {@link CrmCustomerDO#getId()} */ private Long customerId; - /** - * 手机号 - */ - private String mobile; - /** - * 电话 - */ - private String telephone; - /** - * 电子邮箱 - */ - private String email; - /** - * 所在地 - * - * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段 - */ - private Integer areaId; - /** - * 详细地址 - */ - private String detailAddress; - /** - * 备注 - */ - private String remark; - - /** - * 直属上级 - * - * 关联 {@link CrmContactDO#id} - */ - private Long parentId; - /** - * 姓名 - */ - private String name; - /** - * 职位 - */ - private String post; - /** - * QQ - */ - private Long qq; - /** - * 微信 - */ - private String wechat; - /** - * 性别 - * - * 枚举 {@link cn.iocoder.yudao.module.system.enums.common.SexEnum} - */ - private Integer sex; - /** - * 是否关键决策人 - */ - private Boolean master; - /** - * 负责人用户编号 - * - * 关联 AdminUserDO 的 id 字段 - */ - private Long ownerUserId; /** * 最后跟进时间 @@ -114,4 +53,66 @@ public class CrmContactDO extends BaseDO { */ private LocalDateTime contactNextTime; + /** + * 负责人用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + + /** + * 手机号 + */ + private String mobile; + /** + * 电话 + */ + private String telephone; + /** + * 电子邮箱 + */ + private String email; + /** + * QQ + */ + private Long qq; + /** + * 微信 + */ + private String wechat; + /** + * 所在地 + * + * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段 + */ + private Integer areaId; + /** + * 详细地址 + */ + private String detailAddress; + /** + * 性别 + * + * 枚举 {@link cn.iocoder.yudao.module.system.enums.common.SexEnum} + */ + private Integer sex; + /** + * 是否关键决策人 + */ + private Boolean master; + /** + * 职位 + */ + private String post; + /** + * 直属上级 + * + * 关联 {@link CrmContactDO#id} + */ + private Long parentId; + /** + * 备注 + */ + private String remark; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractConfigDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractConfigDO.java new file mode 100644 index 000000000..ab0c2d28e --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractConfigDO.java @@ -0,0 +1,33 @@ +package cn.iocoder.yudao.module.crm.dal.dataobject.contract; + +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.*; +import lombok.*; + +@TableName("crm_contract_config") +@KeySequence("crm_contract_config_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CrmContractConfigDO extends BaseDO { + + /** + * 编号 + */ + @TableId + private Long id; + /** + * 是否开启提前提醒 + */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private Boolean notifyEnabled; + /** + * 提前提醒天数 + */ + @TableField(updateStrategy = FieldStrategy.ALWAYS) + private Integer notifyDays; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java index 0c01a3394..89f4fd98a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java @@ -10,9 +10,9 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; +import java.math.BigDecimal; import java.time.LocalDateTime; -// TODO 芋艿:实体的梳理 /** * CRM 合同 DO * @@ -33,14 +33,14 @@ public class CrmContractDO extends BaseDO { */ @TableId private Long id; - /** - * 合同编号 - */ - private String no; /** * 合同名称 */ private String name; + /** + * 合同编号 + */ + private String no; /** * 客户编号 * @@ -48,17 +48,37 @@ public class CrmContractDO extends BaseDO { */ private Long customerId; /** - * 商机编号 + * 商机编号,非必须 * * 关联 {@link CrmBusinessDO#getId()} */ private Long businessId; + + /** + * 最后跟进时间 + */ + private LocalDateTime contactLastTime; + + /** + * 负责人的用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + /** * 工作流编号 * * 关联 ProcessInstance 的 id 属性 */ private String processInstanceId; + /** + * 审批状态 + * + * 枚举 {@link CrmAuditStatusEnum} + */ + private Integer auditStatus; + /** * 下单日期 */ @@ -72,50 +92,32 @@ public class CrmContractDO extends BaseDO { */ private LocalDateTime endTime; /** - * 合同金额,单位:分 + * 产品总金额,单位:元 */ - private Integer price; + private BigDecimal totalProductPrice; /** * 整单折扣 */ - private Integer discountPercent; + private BigDecimal discountPercent; /** - * 产品总金额,单位:分 + * 合同总金额,单位:分 */ - private Integer productPrice; + private BigDecimal totalPrice; /** - * 客户签约人 + * 客户签约人,非必须 * * 关联 {@link CrmContactDO#getId()} */ - private Long contactId; + private Long signContactId; /** - * 公司签约人 + * 公司签约人,非必须 * * 关联 AdminUserDO 的 id 字段 */ private Long signUserId; - /** - * 最后跟进时间 - */ - private LocalDateTime contactLastTime; /** * 备注 */ private String remark; - /** - * 负责人的用户编号 - * - * 关联 AdminUserDO 的 id 字段 - */ - private Long ownerUserId; - - /** - * 审批状态 - * - * 枚举 {@link CrmAuditStatusEnum} - */ - private Integer auditStatus; - } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java index bc977c78f..6bb3e6bf0 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractProductDO.java @@ -7,8 +7,10 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; +import java.math.BigDecimal; + /** - * 合同产品关联表 DO + * CRM 合同产品关联表 DO * * @author HUIHUI */ @@ -27,12 +29,6 @@ public class CrmContractProductDO extends BaseDO { */ @TableId private Long id; - /** - * 产品编号 - * - * 关联 {@link CrmProductDO#getId()} - */ - private Long productId; /** * 合同编号 * @@ -40,26 +36,28 @@ public class CrmContractProductDO extends BaseDO { */ private Long contractId; /** - * 产品单价 + * 产品编号 + * + * 关联 {@link CrmProductDO#getId()} */ - private Integer price; + private Long productId; /** - * 销售价格, 单位:分 + * 产品单价,单位:元 */ - private Integer salesPrice; + private BigDecimal productPrice; + /** + * 合同价格, 单位:元 + */ + private BigDecimal contractPrice; /** * 数量 */ - private Integer count; + private BigDecimal count; /** - * 折扣 - */ - private Integer discountPercent; - /** - * 总计价格(折扣后价格) + * 总计价格,单位:元 * - * TODO @puhui999:可以写下计算公式哈; + * totalPrice = businessPrice * count */ - private Integer totalPrice; + private BigDecimal totalPrice; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java index 2bd614f57..76d511115 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java @@ -9,8 +9,6 @@ import lombok.*; import java.time.LocalDateTime; -// TODO 芋艿:调整下字段 - /** * CRM 客户 DO * @@ -35,10 +33,35 @@ public class CrmCustomerDO extends BaseDO { * 客户名称 */ private String name; + /** * 跟进状态 */ private Boolean followUpStatus; + /** + * 最后跟进时间 + */ + private LocalDateTime contactLastTime; + /** + * 最后跟进内容 + */ + private String contactLastContent; + /** + * 下次联系时间 + */ + private LocalDateTime contactNextTime; + + /** + * 负责人的用户编号 + * + * 关联 AdminUserDO 的 id 字段 + */ + private Long ownerUserId; + /** + * 成为负责人的时间 + */ + private LocalDateTime ownerTime; + /** * 锁定状态 */ @@ -47,6 +70,37 @@ public class CrmCustomerDO extends BaseDO { * 成交状态 */ private Boolean dealStatus; + + /** + * 手机 + */ + private String mobile; + /** + * 电话 + */ + private String telephone; + /** + * QQ + */ + private String qq; + /** + * wechat + */ + private String wechat; + /** + * email + */ + private String email; + /** + * 所在地 + * + * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段 + */ + private Integer areaId; + /** + * 详细地址 + */ + private String detailAddress; /** * 所属行业 * @@ -65,71 +119,9 @@ public class CrmCustomerDO extends BaseDO { * 对应字典 {@link DictTypeConstants#CRM_CUSTOMER_SOURCE} */ private Integer source; - /** - * 手机 - */ - private String mobile; - /** - * 电话 - */ - private String telephone; - /** - * 网址 - */ - private String website; - /** - * QQ - */ - private String qq; - /** - * wechat - */ - private String wechat; - /** - * email - */ - private String email; - /** - * 客户描述 - */ - private String description; /** * 备注 */ private String remark; - /** - * 负责人的用户编号 - * - * 关联 AdminUserDO 的 id 字段 - */ - private Long ownerUserId; - /** - * 所在地 - * - * 关联 {@link cn.iocoder.yudao.framework.ip.core.Area#getId()} 字段 - */ - private Integer areaId; - /** - * 详细地址 - */ - private String detailAddress; - - /** - * 最后接收时间 - */ - private LocalDateTime receiveTime; - /** - * 最后跟进时间 - */ - private LocalDateTime contactLastTime; - - /** - * 最后跟进内容 - */ - private String contactLastContent; - /** - * 下次联系时间 - */ - private LocalDateTime contactNextTime; } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java index a3c56ccc9..caeeb53d2 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java @@ -8,6 +8,8 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; +import java.math.BigDecimal; + /** * CRM 产品 DO * @@ -43,9 +45,9 @@ public class CrmProductDO extends BaseDO { */ private Integer unit; /** - * 价格,单位:分 + * 价格,单位:元 */ - private Integer price; + private BigDecimal price; /** * 状态 * diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java index 842fc96b4..269cac6cc 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivableDO.java +++ b/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; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; +import cn.iocoder.yudao.module.crm.enums.receivable.CrmReceivableReturnTypeEnum; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; +import java.math.BigDecimal; import java.time.LocalDateTime; /** @@ -33,64 +38,57 @@ public class CrmReceivableDO extends BaseDO { * 回款编号 */ private String no; - // TODO @liuhongfeng:“对应实体”,参考别的模块,关联 {@link TableField.MetaInfo#getJdbcType()} /** - * 回款计划 + * 回款计划编号 * - * TODO @liuhongfeng:这个字段什么时候更新,也可以写下 - * - * 对应实体 {@link CrmReceivablePlanDO} + * 关联 {@link CrmReceivablePlanDO#getId()},非必须 */ private Long planId; /** - * 客户 ID + * 客户编号 * - * 对应实体 {@link cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO} + * 关联 {@link CrmCustomerDO#getId()} */ private Long customerId; /** - * 合同 ID + * 合同编号 * - * 对应实体 {@link CrmContractDO} + * 关联 {@link CrmContractDO#getId()} */ private Long contractId; /** - * 工作流编号 - * - * TODO @liuhongfeng:这个字段,后续要写下关联的实体哈 + * 负责人编号,关联 {@link AdminUserRespDTO#getId()} */ - private Long processInstanceId; + private Long ownerUserId; + /** * 回款日期 */ private LocalDateTime returnTime; - // TODO @liuhongfeng:少个枚举 /** - * 回款方式 + * 回款方式,关联枚举{@link CrmReceivableReturnTypeEnum} */ private Integer returnType; /** - * 回款金额 + * 计划回款金额,单位:元 */ - private Integer price; - // TODO @liuhongfeng:少关联实体; - /** - * 负责人 - */ - private Long ownerUserId; - /** - * 显示顺序 - */ - private Integer sort; - /** - * 审核状态 - * - * 枚举 {@link cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum} - */ - private Integer auditStatus; + private BigDecimal price; /** * 备注 */ private String remark; + /** + * 工作流编号 + * + * 关联 ProcessInstance 的 id 属性 + */ + private String processInstanceId; + /** + * 审批状态 + * + * 枚举 {@link CrmAuditStatusEnum} + */ + private Integer auditStatus; + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java index 78658e609..5ddefc559 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/CrmReceivablePlanDO.java @@ -1,15 +1,19 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.receivable; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; +import cn.iocoder.yudao.module.crm.enums.receivable.CrmReceivableReturnTypeEnum; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.*; +import java.math.BigDecimal; import java.time.LocalDateTime; /** - * 回款计划 DO + * CRM 回款计划 DO * * @author 芋道源码 */ @@ -24,7 +28,7 @@ import java.time.LocalDateTime; public class CrmReceivablePlanDO extends BaseDO { /** - * ID + * 编号 */ @TableId private Long id; @@ -33,23 +37,45 @@ public class CrmReceivablePlanDO extends BaseDO { */ private Integer period; /** - * 回款ID + * 客户编号 * - * TODO @liuhongfeng:少关联实体; + * 关联 {@link CrmCustomerDO#getId()} */ - private Long receivableId; + private Long customerId; /** - * 完成状态 + * 合同编号 + * + * 关联 {@link CrmContractDO#getId()} */ - private Boolean finishStatus; + private Long contractId; + /** - * 计划回款金额,单位:分 + * 负责人编号 + * + * 关联 AdminUserDO 的 id 字段 */ - private Integer price; + private Long ownerUserId; + /** * 计划回款日期 */ private LocalDateTime returnTime; + /** + * 计划回款类型 + * + * 枚举 {@link CrmReceivableReturnTypeEnum} + */ + private Integer returnType; + /** + * 计划回款金额,单位:元 + */ + private BigDecimal price; + + /** + * 回款编号,关联 {@link CrmReceivableDO#getId()} + */ + private Long receivableId; + /** * 提前几天提醒 */ @@ -58,28 +84,6 @@ public class CrmReceivablePlanDO extends BaseDO { * 提醒日期 */ private LocalDateTime remindTime; - /** - * 客户 ID - * - * TODO @liuhongfeng:少关联实体; - */ - private Long customerId; - /** - * 合同 ID - * - * TODO @liuhongfeng:少关联实体; - */ - private Long contractId; - /** - * 负责人 ID - * - * TODO @liuhongfeng:少关联实体; - */ - private Long ownerUserId; - /** - * 显示顺序 - */ - private Integer sort; /** * 备注 */ diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/package-info.java deleted file mode 100644 index f27442cdf..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/receivable/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 回款 - */ -package cn.iocoder.yudao.module.crm.dal.dataobject.receivable; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java index d7438484f..4718a8d7a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessMapper.java @@ -7,12 +7,11 @@ import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; -import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; import java.util.Collection; -import java.util.List; /** * 商机 Mapper @@ -45,7 +44,7 @@ public interface CrmBusinessMapper extends BaseMapperX { default PageResult selectPage(CrmBusinessPageReqVO pageReqVO, Long userId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(), + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(), CrmBusinessDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); // 拼接自身的查询条件 query.selectAll(CrmBusinessDO.class) @@ -54,13 +53,8 @@ public interface CrmBusinessMapper extends BaseMapperX { return selectJoinPage(pageReqVO, CrmBusinessDO.class, query); } - default List selectBatchIds(Collection ids, Long userId) { - MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); - // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_BUSINESS.getType(), ids, userId); - // 拼接自身的查询条件 - query.selectAll(CrmBusinessDO.class).in(CrmBusinessDO::getId, ids).orderByDesc(CrmBusinessDO::getId); - return selectJoinList(CrmBusinessDO.class, query); + default Long selectCountByStatusTypeId(Long statusTypeId) { + return selectCount(CrmBusinessDO::getStatusTypeId, statusTypeId); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java index 2d1471c73..a91a0fd14 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessProductMapper.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.crm.dal.mysql.business; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; import org.apache.ibatis.annotations.Mapper; @@ -16,18 +15,8 @@ import java.util.List; @Mapper public interface CrmBusinessProductMapper extends BaseMapperX { - // TODO @puhui999:用不到的方法,看看是不是删除哈 - default void deleteByBusinessId(Long getBusinessId) { // TODO @lzxhqs:第一个方法,和类之间最好空一行; - delete(CrmBusinessProductDO::getBusinessId, getBusinessId); - } - - default CrmBusinessProductDO selectByBusinessId(Long getBusinessId) { - return selectOne(CrmBusinessProductDO::getBusinessId, getBusinessId); - } - default List selectListByBusinessId(Long businessId) { - // TODO @puhui999:可以简化,selectList(CrmBusinessProductDO::getBusinessId, businessId) - return selectList(new LambdaQueryWrapperX().eq(CrmBusinessProductDO::getBusinessId, businessId)); + return selectList(CrmBusinessProductDO::getBusinessId, businessId); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusMapper.java index be847ab9d..dfe7afcc3 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusMapper.java @@ -1,10 +1,6 @@ package cn.iocoder.yudao.module.crm.dal.mysql.business; -import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; import org.apache.ibatis.annotations.Mapper; @@ -18,20 +14,17 @@ import java.util.List; @Mapper public interface CrmBusinessStatusMapper extends BaseMapperX { - default PageResult selectPage(CrmBusinessStatusPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .orderByDesc(CrmBusinessStatusDO::getId)); - } - - default List selectList(CrmBusinessStatusQueryVO queryVO) { - return selectList(new LambdaQueryWrapperX() - .eqIfPresent(CrmBusinessStatusDO::getTypeId, queryVO.getTypeId()) - .inIfPresent(CrmBusinessStatusDO::getId, queryVO.getIdList()) - .orderByDesc(CrmBusinessStatusDO::getId)); - } - - default int delete(Long typeId) { + default int deleteByTypeId(Long typeId) { return delete(CrmBusinessStatusDO::getTypeId, typeId); } + default List selectListByTypeId(Long typeId) { + return selectList(CrmBusinessStatusDO::getTypeId, typeId); + } + + default CrmBusinessStatusDO selectByTypeIdAndId(Long statusTypeId, Long statusId) { + return selectOne(CrmBusinessStatusDO::getTypeId, statusTypeId, + CrmBusinessStatusDO::getId, statusId); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java index 410ebf050..3444e58a7 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/CrmBusinessStatusTypeMapper.java @@ -1,44 +1,27 @@ package cn.iocoder.yudao.module.crm.dal.mysql.business; +import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; import org.apache.ibatis.annotations.Mapper; -import java.util.List; - /** - * 商机状态类型 Mapper + * 商机状态组 Mapper * * @author ljlleo */ @Mapper public interface CrmBusinessStatusTypeMapper extends BaseMapperX { - default PageResult selectPage(CrmBusinessStatusTypePageReqVO reqVO) { + default PageResult selectPage(PageParam reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .orderByDesc(CrmBusinessStatusTypeDO::getId)); } - default List selectList(CrmBusinessStatusTypeQueryVO queryVO) { - return selectList(new LambdaQueryWrapperX() - .eqIfPresent(CrmBusinessStatusTypeDO::getStatus, queryVO.getStatus()) - .inIfPresent(CrmBusinessStatusTypeDO::getId, queryVO.getIdList())); - } - - // TODO @lzxhqs:这个可以改成 selectByName。业务上基于在判断 id 匹配;这样更通用一些;mapper 尽量通用,不关注或者特别关联业务; - /** - * 根据ID和name查询 - * - * @param id 商机状态类型id - * @param name 状态类型名 - * @return result - */ - default CrmBusinessStatusTypeDO selectByIdAndName(Long id, String name) { - return selectOne(CrmBusinessStatusTypeDO::getId, id, CrmBusinessStatusTypeDO::getName, name); + default CrmBusinessStatusTypeDO selectByName(String name) { + return selectOne(CrmBusinessStatusTypeDO::getName, name); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/package-info.java deleted file mode 100644 index 72863e1f4..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/business/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 商机(销售机会) - */ -package cn.iocoder.yudao.module.crm.dal.mysql.business; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java index cab4c4662..d0665c604 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/clue/CrmClueMapper.java @@ -6,8 +6,8 @@ import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; -import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; import org.apache.ibatis.annotations.Mapper; import java.util.Collection; @@ -21,25 +21,21 @@ import java.util.List; @Mapper public interface CrmClueMapper extends BaseMapperX { - default int updateOwnerUserIdById(Long id, Long ownerUserId) { - return update(new LambdaUpdateWrapper() - .eq(CrmClueDO::getId, id) - .set(CrmClueDO::getOwnerUserId, ownerUserId)); - } - default PageResult selectPage(CrmCluePageReqVO pageReqVO, Long userId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(), + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CLUE.getType(), CrmClueDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool()); // 拼接自身的查询条件 query.selectAll(CrmClueDO.class) .likeIfPresent(CrmClueDO::getName, pageReqVO.getName()) + .eqIfPresent(CrmClueDO::getTransformStatus, pageReqVO.getTransformStatus()) .likeIfPresent(CrmClueDO::getTelephone, pageReqVO.getTelephone()) .likeIfPresent(CrmClueDO::getMobile, pageReqVO.getMobile()) .eqIfPresent(CrmClueDO::getIndustryId, pageReqVO.getIndustryId()) .eqIfPresent(CrmClueDO::getLevel, pageReqVO.getLevel()) .eqIfPresent(CrmClueDO::getSource, pageReqVO.getSource()) + .eqIfPresent(CrmClueDO::getFollowUpStatus, pageReqVO.getFollowUpStatus()) .orderByDesc(CrmClueDO::getId); return selectJoinPage(pageReqVO, CrmClueDO.class, query); } @@ -47,10 +43,21 @@ public interface CrmClueMapper extends BaseMapperX { default List selectBatchIds(Collection ids, Long userId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_LEADS.getType(), ids, userId); + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CLUE.getType(), ids, userId); query.selectAll(CrmClueDO.class).in(CrmClueDO::getId, ids).orderByDesc(CrmClueDO::getId); // 拼接自身的查询条件 return selectJoinList(CrmClueDO.class, query); } + default Long selectCountByFollow(Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 我负责的 + 非公海 + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CLUE.getType(), + CrmClueDO::getId, userId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE); + // 未跟进 + 未转化 + query.eq(CrmClueDO::getFollowUpStatus, false) + .eq(CrmClueDO::getTransformStatus, false); + return selectCount(query); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactBusinessMapper.java similarity index 67% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactBusinessMapper.java index 3eae483bc..7bff0c204 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contactbusinesslink/CrmContactBusinessMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactBusinessMapper.java @@ -1,4 +1,4 @@ -package cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink; +package cn.iocoder.yudao.module.crm.dal.mysql.contact; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; @@ -9,7 +9,7 @@ import java.util.Collection; import java.util.List; /** - * CRM 联系人与商机的关联 Mapper + * CRM 联系人商机关联 Mapper * * @author 芋道源码 */ @@ -27,8 +27,18 @@ public interface CrmContactBusinessMapper extends BaseMapperX contactIds) { + delete(new LambdaQueryWrapper() + .eq(CrmContactBusinessDO::getBusinessId, businessId) + .in(CrmContactBusinessDO::getContactId, contactIds)); + } + default List selectListByContactId(Long contactId) { return selectList(CrmContactBusinessDO::getContactId, contactId); } -} \ No newline at end of file + default List selectListByBusinessId(Long businessId) { + return selectList(CrmContactBusinessDO::getBusinessId, businessId); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java index c35df47d3..4a77665ad 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/CrmContactMapper.java @@ -7,7 +7,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; -import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; @@ -22,12 +22,6 @@ import java.util.List; @Mapper public interface CrmContactMapper extends BaseMapperX { - default int updateOwnerUserIdById(Long id, Long ownerUserId) { - return update(new LambdaUpdateWrapper() - .eq(CrmContactDO::getId, id) - .set(CrmContactDO::getOwnerUserId, ownerUserId)); - } - default int updateOwnerUserIdByCustomerId(Long customerId, Long ownerUserId) { return update(new LambdaUpdateWrapper() .eq(CrmContactDO::getCustomerId, customerId) @@ -46,10 +40,22 @@ public interface CrmContactMapper extends BaseMapperX { .orderByDesc(CrmContactDO::getId)); } + default PageResult selectPageByBusinessId(CrmContactPageReqVO pageVO, Collection ids) { + return selectPage(pageVO, new LambdaQueryWrapperX() + .in(CrmContactDO::getId, ids) // 指定联系人编号 + .likeIfPresent(CrmContactDO::getName, pageVO.getName()) + .eqIfPresent(CrmContactDO::getMobile, pageVO.getMobile()) + .eqIfPresent(CrmContactDO::getTelephone, pageVO.getTelephone()) + .eqIfPresent(CrmContactDO::getEmail, pageVO.getEmail()) + .eqIfPresent(CrmContactDO::getQq, pageVO.getQq()) + .eqIfPresent(CrmContactDO::getWechat, pageVO.getWechat()) + .orderByDesc(CrmContactDO::getId)); + } + default PageResult selectPage(CrmContactPageReqVO pageReqVO, Long userId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(), + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(), CrmContactDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); // 拼接自身的查询条件 query.selectAll(CrmContactDO.class) @@ -63,13 +69,8 @@ public interface CrmContactMapper extends BaseMapperX { return selectJoinPage(pageReqVO, CrmContactDO.class, query); } - default List selectBatchIds(Collection ids, Long userId) { - MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); - // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(), ids, userId); - // 拼接自身的查询条件 - query.selectAll(CrmContactDO.class).in(CrmContactDO::getId, ids).orderByDesc(CrmContactDO::getId); - return selectJoinList(CrmContactDO.class, query); + default List selectListByCustomerId(Long customerId) { + return selectList(CrmContactDO::getCustomerId, customerId); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/package-info.java deleted file mode 100644 index 6cb7d4be2..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contact/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * 联系人 - */ -package cn.iocoder.yudao.module.crm.dal.mysql.contact; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractConfigMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractConfigMapper.java new file mode 100644 index 000000000..64e6f918b --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractConfigMapper.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.module.crm.dal.mysql.contract; + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO; +import org.apache.ibatis.annotations.Mapper; + +/** + * 合同配置 Mapper + * + * @author Wanwan + */ +@Mapper +public interface CrmContractConfigMapper extends BaseMapperX { + + default CrmContractConfigDO selectOne() { + return selectOne(new QueryWrapperX().limitN(1)); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java index 3ecd93fcb..e06afb257 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java @@ -5,12 +5,13 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; -import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; import org.apache.ibatis.annotations.Mapper; import java.time.LocalDateTime; @@ -25,10 +26,8 @@ import java.util.List; @Mapper public interface CrmContractMapper extends BaseMapperX { - default int updateOwnerUserIdById(Long id, Long ownerUserId) { - return update(new LambdaUpdateWrapper() - .eq(CrmContractDO::getId, id) - .set(CrmContractDO::getOwnerUserId, ownerUserId)); + default CrmContractDO selectByNo(String no) { + return selectOne(CrmContractDO::getNo, no); } default PageResult selectPageByCustomerId(CrmContractPageReqVO pageReqVO) { @@ -41,10 +40,20 @@ public interface CrmContractMapper extends BaseMapperX { .orderByDesc(CrmContractDO::getId)); } - default PageResult selectPage(CrmContractPageReqVO pageReqVO, Long userId) { + default PageResult selectPageByBusinessId(CrmContractPageReqVO pageReqVO) { + return selectPage(pageReqVO, new LambdaQueryWrapperX() + .eq(CrmContractDO::getBusinessId, pageReqVO.getBusinessId()) + .likeIfPresent(CrmContractDO::getNo, pageReqVO.getNo()) + .likeIfPresent(CrmContractDO::getName, pageReqVO.getName()) + .eqIfPresent(CrmContractDO::getCustomerId, pageReqVO.getCustomerId()) + .eqIfPresent(CrmContractDO::getBusinessId, pageReqVO.getBusinessId()) + .orderByDesc(CrmContractDO::getId)); + } + + default PageResult selectPage(CrmContractPageReqVO pageReqVO, Long userId, CrmContractConfigDO config) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(), + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(), CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); // 拼接自身的查询条件 query.selectAll(CrmContractDO.class) @@ -59,10 +68,8 @@ public interface CrmContractMapper extends BaseMapperX { LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now()); if (CrmContractPageReqVO.EXPIRY_TYPE_ABOUT_TO_EXPIRE.equals(pageReqVO.getExpiryType())) { // 即将到期 - // TODO: @芋艿 需要配置 提前提醒天数 - int REMIND_DAYS = 20; query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus()) - .between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(REMIND_DAYS)); + .between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(config.getNotifyDays())); } else if (CrmContractPageReqVO.EXPIRY_TYPE_EXPIRED.equals(pageReqVO.getExpiryType())) { // 已到期 query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus()) .lt(CrmContractDO::getEndTime, endOfToday); @@ -73,18 +80,41 @@ public interface CrmContractMapper extends BaseMapperX { default List selectBatchIds(Collection ids, Long userId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 构建数据权限连表条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(), ids, userId); + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(), ids, userId); // 拼接自身的查询条件 query.selectAll(CrmContractDO.class).in(CrmContractDO::getId, ids).orderByDesc(CrmContractDO::getId); return selectJoinList(CrmContractDO.class, query); } default Long selectCountByContactId(Long contactId) { - return selectCount(CrmContractDO::getContactId, contactId); + return selectCount(CrmContractDO::getSignContactId, contactId); } default Long selectCountByBusinessId(Long businessId) { return selectCount(CrmContractDO::getBusinessId, businessId); } + default Long selectCountByAudit(Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 我负责的 + 非公海 + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(), + CrmContractDO::getId, userId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE); + // 未审核 + query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.PROCESS.getStatus()); + return selectCount(query); + } + + default Long selectCountByRemind(Long userId, CrmContractConfigDO config) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 我负责的 + 非公海 + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(), + CrmContractDO::getId, userId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE); + // 即将到期 + LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); + LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now()); + query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.APPROVE.getStatus()) // 必须审批通过! + .between(CrmContractDO::getEndTime, beginOfToday, endOfToday.plusDays(config.getNotifyDays())); + return selectCount(query); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java index 814024125..feafbc444 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractProductMapper.java @@ -16,15 +16,6 @@ import java.util.List; @Mapper public interface CrmContractProductMapper extends BaseMapperX { - // TODO @puhui999:用不到的方法,看看是不是删除哈 - default void deleteByContractId(Long contractId) { // TODO @lzxhqs:第一个方法,和类之间最好空一行; - delete(CrmContractProductDO::getContractId, contractId); - } - - default CrmContractProductDO selectByContractId(Long contractId) { - return selectOne(CrmContractProductDO::getContractId, contractId); - } - default List selectListByContractId(Long contractId) { return selectList(new LambdaQueryWrapperX().eq(CrmContractProductDO::getContractId, contractId)); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java index 29cd47c66..615783950 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java @@ -1,20 +1,23 @@ package cn.iocoder.yudao.module.crm.dal.mysql.customer; import cn.hutool.core.date.LocalDateTimeUtil; +import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; -import cn.iocoder.yudao.module.crm.controller.admin.backlog.vo.CrmTodayCustomerPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.CrmCustomerPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; -import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; import org.springframework.lang.Nullable; +import org.springframework.util.Assert; import java.time.LocalDateTime; import java.util.Collection; @@ -46,53 +49,30 @@ public interface CrmCustomerMapper extends BaseMapperX { .set(CrmCustomerDO::getOwnerUserId, ownerUserId)); } - default PageResult selectPage(CrmCustomerPageReqVO pageReqVO, Long userId) { + default PageResult selectPage(CrmCustomerPageReqVO pageReqVO, Long ownerUserId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), - CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), pageReqVO.getPool()); + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), + CrmCustomerDO::getId, ownerUserId, pageReqVO.getSceneType(), pageReqVO.getPool()); // 拼接自身的查询条件 query.selectAll(CrmCustomerDO.class) .likeIfPresent(CrmCustomerDO::getName, pageReqVO.getName()) .eqIfPresent(CrmCustomerDO::getMobile, pageReqVO.getMobile()) .eqIfPresent(CrmCustomerDO::getIndustryId, pageReqVO.getIndustryId()) .eqIfPresent(CrmCustomerDO::getLevel, pageReqVO.getLevel()) - .eqIfPresent(CrmCustomerDO::getSource, pageReqVO.getSource()); - return selectJoinPage(pageReqVO, CrmCustomerDO.class, query); - } + .eqIfPresent(CrmCustomerDO::getSource, pageReqVO.getSource()) + .eqIfPresent(CrmCustomerDO::getFollowUpStatus, pageReqVO.getFollowUpStatus()); - default List selectBatchIds(Collection ids, Long userId) { - MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); - // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), ids, userId); - // 拼接自身的查询条件 - query.selectAll(CrmCustomerDO.class).in(CrmCustomerDO::getId, ids).orderByDesc(CrmCustomerDO::getId); - return selectJoinList(CrmCustomerDO.class, query); - } - - /** - * 待办事项 - 今日需联系客户 - * - * @param pageReqVO 分页请求参数 - * @param userId 当前用户ID - * @return 分页结果 - */ - default PageResult selectTodayCustomerPage(CrmTodayCustomerPageReqVO pageReqVO, Long userId) { - MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); - // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), - CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), null); - - // 拼接自身的查询条件 - query.selectAll(CrmCustomerDO.class); - if (pageReqVO.getContactStatus() != null) { + // backlog 查询 + if (ObjUtil.isNotNull(pageReqVO.getContactStatus())) { + Assert.isNull(pageReqVO.getPool(), "pool 必须是 null"); LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now()); - if (pageReqVO.getContactStatus().equals(CrmTodayCustomerPageReqVO.CONTACT_TODAY)) { // 今天需联系 + if (pageReqVO.getContactStatus().equals(CrmCustomerPageReqVO.CONTACT_TODAY)) { // 今天需联系 query.between(CrmCustomerDO::getContactNextTime, beginOfToday, endOfToday); - } else if (pageReqVO.getContactStatus().equals(CrmTodayCustomerPageReqVO.CONTACT_EXPIRED)) { // 已逾期 + } else if (pageReqVO.getContactStatus().equals(CrmCustomerPageReqVO.CONTACT_EXPIRED)) { // 已逾期 query.lt(CrmCustomerDO::getContactNextTime, beginOfToday); - } else if (pageReqVO.getContactStatus().equals(CrmTodayCustomerPageReqVO.CONTACT_ALREADY)) { // 已联系 + } else if (pageReqVO.getContactStatus().equals(CrmCustomerPageReqVO.CONTACT_ALREADY)) { // 已联系 query.between(CrmCustomerDO::getContactLastTime, beginOfToday, endOfToday); } else { throw new IllegalArgumentException("未知联系状态:" + pageReqVO.getContactStatus()); @@ -101,50 +81,109 @@ public interface CrmCustomerMapper extends BaseMapperX { return selectJoinPage(pageReqVO, CrmCustomerDO.class, query); } - default List selectListByLockAndNotPool(Boolean lockStatus) { - return selectList(new LambdaQueryWrapper() - .eq(CrmCustomerDO::getLockStatus, lockStatus) - .gt(CrmCustomerDO::getOwnerUserId, 0)); + default List selectBatchIds(Collection ids, Long ownerUserId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 拼接数据权限的查询条件 + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), ids, ownerUserId); + // 拼接自身的查询条件 + query.selectAll(CrmCustomerDO.class).in(CrmCustomerDO::getId, ids).orderByDesc(CrmCustomerDO::getId); + return selectJoinList(CrmCustomerDO.class, query); } default CrmCustomerDO selectByCustomerName(String name) { return selectOne(CrmCustomerDO::getName, name); } - default PageResult selectPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO, - CrmCustomerPoolConfigDO poolConfigDO, - Long userId) { + default PageResult selectPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO, + CrmCustomerPoolConfigDO poolConfig, + Long ownerUserId) { + final MPJLambdaWrapperX query = buildPutPoolRemindCustomerQuery(pageReqVO, poolConfig, ownerUserId); + return selectJoinPage(pageReqVO, CrmCustomerDO.class, query.selectAll(CrmCustomerDO.class)); + } + + default Long selectPutPoolRemindCustomerCount(CrmCustomerPageReqVO pageReqVO, + CrmCustomerPoolConfigDO poolConfigDO, + Long userId) { + final MPJLambdaWrapperX query = buildPutPoolRemindCustomerQuery(pageReqVO, poolConfigDO, userId); + return selectCount(query); + } + + private static MPJLambdaWrapperX buildPutPoolRemindCustomerQuery(CrmCustomerPageReqVO pageReqVO, + CrmCustomerPoolConfigDO poolConfig, + Long ownerUserId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), - CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), null); - // TODO @dhb52:lock 的情况,不需要提醒哈; + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), + CrmCustomerDO::getId, ownerUserId, pageReqVO.getSceneType(), null); + + // 未锁定 + 未成交 + query.eq(CrmCustomerDO::getLockStatus, false).eq(CrmCustomerDO::getDealStatus, false); - // 拼接自身的查询条件 - query.selectAll(CrmCustomerDO.class); // 情况一:未成交提醒日期区间 - Integer dealExpireDays = poolConfigDO.getDealExpireDays(); - LocalDateTime startDealRemindDate = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()) - .minusDays(dealExpireDays); - LocalDateTime endDealRemindDate = LocalDateTimeUtil.endOfDay(LocalDateTime.now()) - .minusDays(Math.max(dealExpireDays - poolConfigDO.getNotifyDays(), 0)); + Integer dealExpireDays = poolConfig.getDealExpireDays(); + LocalDateTime startDealRemindTime = LocalDateTime.now().minusDays(dealExpireDays); + LocalDateTime endDealRemindTime = LocalDateTime.now() + .minusDays(Math.max(dealExpireDays - poolConfig.getNotifyDays(), 0)); // 情况二:未跟进提醒日期区间 - Integer contactExpireDays = poolConfigDO.getContactExpireDays(); - LocalDateTime startContactRemindDate = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()) - .minusDays(contactExpireDays); - LocalDateTime endContactRemindDate = LocalDateTimeUtil.endOfDay(LocalDateTime.now()) - .minusDays(Math.max(contactExpireDays - poolConfigDO.getNotifyDays(), 0)); - query - // 情况一:1. 未成交放入公海提醒 - .eq(CrmCustomerDO::getDealStatus, false) - .between(CrmCustomerDO::getCreateTime, startDealRemindDate, endDealRemindDate) - // 情况二:未跟进放入公海提醒 - .or() // 2.1 contactLastTime 为空 TODO 芋艿:这个要不要搞个默认值; - .isNull(CrmCustomerDO::getContactLastTime) - .between(CrmCustomerDO::getCreateTime, startContactRemindDate, endContactRemindDate) - .or() // 2.2 ContactLastTime 不为空 - .between(CrmCustomerDO::getContactLastTime, startContactRemindDate, endContactRemindDate); - return selectJoinPage(pageReqVO, CrmCustomerDO.class, query); + Integer contactExpireDays = poolConfig.getContactExpireDays(); + LocalDateTime startContactRemindTime = LocalDateTime.now().minusDays(contactExpireDays); + LocalDateTime endContactRemindTime = LocalDateTime.now() + .minusDays(Math.max(contactExpireDays - poolConfig.getNotifyDays(), 0)); + query.and(q -> { + // 情况一:成交超时提醒 + q.between(CrmCustomerDO::getOwnerTime, startDealRemindTime, endDealRemindTime) + // 情况二:跟进超时提醒 + .or(w -> w.between(CrmCustomerDO::getOwnerTime, startContactRemindTime, endContactRemindTime) + .and(p -> p.between(CrmCustomerDO::getContactLastTime, startContactRemindTime, endContactRemindTime) + .or().isNull(CrmCustomerDO::getContactLastTime))); + }); + return query; + } + + /** + * 获得需要过期到公海的客户列表 + * + * @return 客户列表 + */ + default List selectListByAutoPool(CrmCustomerPoolConfigDO poolConfig) { + LambdaQueryWrapper query = new LambdaQueryWrapper<>(); + query.gt(CrmCustomerDO::getOwnerUserId, 0); + // 未锁定 + 未成交 + query.eq(CrmCustomerDO::getLockStatus, false).eq(CrmCustomerDO::getDealStatus, false); + // 已经超时 + LocalDateTime dealExpireTime = LocalDateTime.now().minusDays(poolConfig.getDealExpireDays()); + LocalDateTime contactExpireTime = LocalDateTime.now().minusDays(poolConfig.getContactExpireDays()); + query.and(q -> { + // 情况一:成交超时 + q.lt(CrmCustomerDO::getOwnerTime, dealExpireTime) + // 情况二:跟进超时 + .or(w -> w.lt(CrmCustomerDO::getOwnerTime, contactExpireTime) + .and(p -> p.lt(CrmCustomerDO::getContactLastTime, contactExpireTime) + .or().isNull(CrmCustomerDO::getContactLastTime))); + }); + return selectList(query); + } + + default Long selectCountByTodayContact(Long ownerUserId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 我负责的 + 非公海 + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), + CrmCustomerDO::getId, ownerUserId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE); + // 今天需联系 + LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); + LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now()); + query.between(CrmCustomerDO::getContactNextTime, beginOfToday, endOfToday); + return selectCount(query); + } + + default Long selectCountByFollow(Long ownerUserId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 我负责的 + 非公海 + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), + CrmCustomerDO::getId, ownerUserId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE); + // 未跟进 + query.eq(CrmClueDO::getFollowUpStatus, false); + return selectCount(query); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java index 30a07eec2..4d1d61809 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/product/CrmProductMapper.java @@ -5,10 +5,10 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; -import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; -import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; import org.apache.ibatis.annotations.Mapper; +import java.util.List; + /** * CRM 产品 Mapper * @@ -17,21 +17,23 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface CrmProductMapper extends BaseMapperX { - default PageResult selectPage(CrmProductPageReqVO reqVO, Long userId) { - MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); - // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_PRODUCT.getType(), - CrmProductDO::getId, userId, null, Boolean.FALSE); - // 拼接自身的查询条件 - query.selectAll(CrmProductDO.class) + default PageResult selectPage(CrmProductPageReqVO reqVO) { + return selectPage(reqVO, new MPJLambdaWrapperX() .likeIfPresent(CrmProductDO::getName, reqVO.getName()) .eqIfPresent(CrmProductDO::getStatus, reqVO.getStatus()) - .orderByDesc(CrmProductDO::getId); - return selectJoinPage(reqVO, CrmProductDO.class, query); + .orderByDesc(CrmProductDO::getId)); } default CrmProductDO selectByNo(String no) { return selectOne(CrmProductDO::getNo, no); } + default Long selectCountByCategoryId(Long categoryId) { + return selectCount(CrmProductDO::getCategoryId, categoryId); + } + + default List selectListByStatus(Integer status) { + return selectList(CrmProductDO::getStatus, status); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java index 28b2298ec..5a5e9ce2b 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivableMapper.java @@ -1,18 +1,27 @@ package cn.iocoder.yudao.module.crm.dal.mysql.receivable; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; -import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import org.apache.ibatis.annotations.Mapper; +import java.math.BigDecimal; import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * 回款 Mapper @@ -22,16 +31,15 @@ import java.util.List; @Mapper public interface CrmReceivableMapper extends BaseMapperX { - default int updateOwnerUserIdById(Long id, Long ownerUserId) { - return update(new LambdaUpdateWrapper() - .eq(CrmReceivableDO::getId, id) - .set(CrmReceivableDO::getOwnerUserId, ownerUserId)); + default CrmReceivableDO selectByNo(String no) { + return selectOne(CrmReceivableDO::getNo, no); } default PageResult selectPageByCustomerId(CrmReceivablePageReqVO reqVO) { return selectPage(reqVO, new LambdaQueryWrapperX() .eq(CrmReceivableDO::getCustomerId, reqVO.getCustomerId()) // 必须传递 .eqIfPresent(CrmReceivableDO::getNo, reqVO.getNo()) + .eqIfPresent(CrmReceivableDO::getContractId, reqVO.getContractId()) .eqIfPresent(CrmReceivableDO::getPlanId, reqVO.getPlanId()) .orderByDesc(CrmReceivableDO::getId)); } @@ -39,12 +47,13 @@ public interface CrmReceivableMapper extends BaseMapperX { default PageResult selectPage(CrmReceivablePageReqVO pageReqVO, Long userId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), CrmReceivableDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); // 拼接自身的查询条件 query.selectAll(CrmReceivableDO.class) .eqIfPresent(CrmReceivableDO::getNo, pageReqVO.getNo()) .eqIfPresent(CrmReceivableDO::getPlanId, pageReqVO.getPlanId()) + .eqIfPresent(CrmReceivableDO::getContractId, pageReqVO.getContractId()) .eqIfPresent(CrmReceivableDO::getAuditStatus, pageReqVO.getAuditStatus()) .orderByDesc(CrmReceivableDO::getId); return selectJoinPage(pageReqVO, CrmReceivableDO.class, query); @@ -53,10 +62,41 @@ public interface CrmReceivableMapper extends BaseMapperX { default List selectBatchIds(Collection ids, Long userId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), ids, userId); + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), ids, userId); // 拼接自身的查询条件 query.selectAll(CrmReceivableDO.class).in(CrmReceivableDO::getId, ids).orderByDesc(CrmReceivableDO::getId); return selectJoinList(CrmReceivableDO.class, query); } + default Long selectCountByAudit(Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 我负责的 + 非公海 + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE.getType(), + CrmReceivableDO::getId, userId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE); + // 未审核 + query.eq(CrmContractDO::getAuditStatus, CrmAuditStatusEnum.PROCESS.getStatus()); + return selectCount(query); + } + + default List selectListByContractIdAndStatus(Long contractId, Collection auditStatuses) { + return selectList(new LambdaQueryWrapperX() + .eq(CrmReceivableDO::getContractId, contractId) + .in(CrmReceivableDO::getAuditStatus, auditStatuses)); + } + + default Map selectReceivablePriceMapByContractId(Collection contractIds) { + if (CollUtil.isEmpty(contractIds)) { + return Collections.emptyMap(); + } + // SQL sum 查询 + List> result = selectMaps(new QueryWrapper() + .select("contract_id, SUM(price) AS total_price") + .in("audit_status", CrmAuditStatusEnum.DRAFT.getStatus(), // 草稿 + 审批中 + 审批通过 + CrmAuditStatusEnum.PROCESS, CrmAuditStatusEnum.APPROVE.getStatus()) + .groupBy("contract_id") + .in("contract_id", contractIds)); + // 获得金额 + return convertMap(result, obj -> (Long) obj.get("contract_id"), obj -> (BigDecimal) obj.get("total_price")); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java index b21d30c12..4d5389793 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/receivable/CrmReceivablePlanMapper.java @@ -3,18 +3,19 @@ package cn.iocoder.yudao.module.crm.dal.mysql.receivable; import cn.hutool.core.date.LocalDateTimeUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.query.MPJLambdaWrapperX; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; -import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; +import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; import org.apache.ibatis.annotations.Mapper; import java.time.LocalDateTime; import java.util.Collection; import java.util.List; +import java.util.Objects; /** * 回款计划 Mapper @@ -24,58 +25,75 @@ import java.util.List; @Mapper public interface CrmReceivablePlanMapper extends BaseMapperX { - default int updateOwnerUserIdById(Long id, Long ownerUserId) { - return update(new LambdaUpdateWrapper() - .eq(CrmReceivablePlanDO::getId, id) - .set(CrmReceivablePlanDO::getOwnerUserId, ownerUserId)); + default CrmReceivablePlanDO selectMaxPeriodByContractId(Long contractId) { + return selectOne(new MPJLambdaWrapperX() + .eq(CrmReceivablePlanDO::getContractId, contractId) + .orderByDesc(CrmReceivablePlanDO::getPeriod) + .last("LIMIT 1")); } default PageResult selectPageByCustomerId(CrmReceivablePlanPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eq(CrmReceivablePlanDO::getCustomerId, reqVO.getCustomerId()) // 必须传递 + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + if (Objects.nonNull(reqVO.getContractNo())) { // 根据合同编号检索 + query.innerJoin(CrmContractDO.class, on -> on.like(CrmContractDO::getNo, reqVO.getContractNo()) + .eq(CrmContractDO::getId, CrmReceivablePlanDO::getContractId)); + } + query.eq(CrmReceivablePlanDO::getCustomerId, reqVO.getCustomerId()) // 必须传递 .eqIfPresent(CrmReceivablePlanDO::getContractId, reqVO.getContractId()) - .orderByDesc(CrmReceivablePlanDO::getId)); + .orderByDesc(CrmReceivablePlanDO::getPeriod); + return selectJoinPage(reqVO, CrmReceivablePlanDO.class, query); } default PageResult selectPage(CrmReceivablePlanPageReqVO pageReqVO, Long userId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), CrmReceivablePlanDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE); // 拼接自身的查询条件 query.selectAll(CrmReceivablePlanDO.class) .eqIfPresent(CrmReceivablePlanDO::getCustomerId, pageReqVO.getCustomerId()) .eqIfPresent(CrmReceivablePlanDO::getContractId, pageReqVO.getContractId()) - .orderByDesc(CrmReceivablePlanDO::getId); + .orderByDesc(CrmReceivablePlanDO::getPeriod); + if (Objects.nonNull(pageReqVO.getContractNo())) { // 根据合同编号检索 + query.innerJoin(CrmContractDO.class, on -> on.like(CrmContractDO::getNo, pageReqVO.getContractNo()) + .eq(CrmContractDO::getId, CrmReceivablePlanDO::getContractId)); + } // Backlog: 回款提醒类型 LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); - LocalDateTime endOfToday = LocalDateTimeUtil.endOfDay(LocalDateTime.now()); if (CrmReceivablePlanPageReqVO.REMIND_TYPE_NEEDED.equals(pageReqVO.getRemindType())) { // 待回款 - query.isNull(CrmReceivablePlanDO::getReceivableId) - .gt(CrmReceivablePlanDO::getReturnTime, beginOfToday) - // TODO @dhb52:这里看看怎么改成不要使用 to_days - .apply("to_days(return_time) <= to_days(now())+ remind_days"); + query.isNull(CrmReceivablePlanDO::getReceivableId) // 未回款 + .lt(CrmReceivablePlanDO::getReturnTime, beginOfToday) // 已逾期 + .lt(CrmReceivablePlanDO::getRemindTime, beginOfToday); // 今天开始提醒 } else if (CrmReceivablePlanPageReqVO.REMIND_TYPE_EXPIRED.equals(pageReqVO.getRemindType())) { // 已逾期 - query.isNull(CrmReceivablePlanDO::getReceivableId) - .lt(CrmReceivablePlanDO::getReturnTime, endOfToday); + query.isNull(CrmReceivablePlanDO::getReceivableId) // 未回款 + .ge(CrmReceivablePlanDO::getReturnTime, beginOfToday); // 已逾期 } else if (CrmReceivablePlanPageReqVO.REMIND_TYPE_RECEIVED.equals(pageReqVO.getRemindType())) { // 已回款 - query.isNotNull(CrmReceivablePlanDO::getReceivableId) - .gt(CrmReceivablePlanDO::getReturnTime, beginOfToday) - // TODO @dhb52:这里看看怎么改成不要使用 to_days - .apply("to_days(return_time) <= to_days(now()) + remind_days"); + query.isNotNull(CrmReceivablePlanDO::getReceivableId); } - return selectJoinPage(pageReqVO, CrmReceivablePlanDO.class, query); } default List selectBatchIds(Collection ids, Long userId) { MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); // 拼接数据权限的查询条件 - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), ids, userId); + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), ids, userId); // 拼接自身的查询条件 query.selectAll(CrmReceivablePlanDO.class).in(CrmReceivablePlanDO::getId, ids).orderByDesc(CrmReceivablePlanDO::getId); return selectJoinList(CrmReceivablePlanDO.class, query); } + default Long selectReceivablePlanCountByRemind(Long userId) { + MPJLambdaWrapperX query = new MPJLambdaWrapperX<>(); + // 我负责的 + 非公海 + CrmPermissionUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), + CrmReceivablePlanDO::getId, userId, CrmSceneTypeEnum.OWNER.getType(), Boolean.FALSE); + // 未回款 + 已逾期 + 今天开始提醒 + LocalDateTime beginOfToday = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()); + query.isNull(CrmReceivablePlanDO::getReceivableId) // 未回款 + .lt(CrmReceivablePlanDO::getReturnTime, beginOfToday) // 已逾期 + .lt(CrmReceivablePlanDO::getRemindTime, beginOfToday); // 今天开始提醒 + return selectCount(query); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankingMapper.java similarity index 52% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankingMapper.java index 9b71df7b6..4b51ab2fe 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/bi/CrmBiRankingMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsRankingMapper.java @@ -1,18 +1,18 @@ -package cn.iocoder.yudao.module.crm.dal.mysql.bi; +package cn.iocoder.yudao.module.crm.dal.mysql.statistics; -import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO; import org.apache.ibatis.annotations.Mapper; import java.util.List; /** - * CRM BI 排行榜 Mapper + * CRM 排行榜统计 Mapper * * @author anhaohao */ @Mapper -public interface CrmBiRankingMapper { +public interface CrmStatisticsRankingMapper { /** * 查询合同金额排行榜 @@ -20,7 +20,7 @@ public interface CrmBiRankingMapper { * @param rankReqVO 参数 * @return 合同金额排行榜 */ - List selectContractPriceRank(CrmBiRankReqVO rankReqVO); + List selectContractPriceRank(CrmStatisticsRankReqVO rankReqVO); /** * 查询回款金额排行榜 @@ -28,7 +28,7 @@ public interface CrmBiRankingMapper { * @param rankReqVO 参数 * @return 回款金额排行榜 */ - List selectReceivablePriceRank(CrmBiRankReqVO rankReqVO); + List selectReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO); /** * 查询签约合同数量排行榜 @@ -36,7 +36,7 @@ public interface CrmBiRankingMapper { * @param rankReqVO 参数 * @return 签约合同数量排行榜 */ - List selectContractCountRank(CrmBiRankReqVO rankReqVO); + List selectContractCountRank(CrmStatisticsRankReqVO rankReqVO); /** * 查询产品销量排行榜 @@ -44,7 +44,7 @@ public interface CrmBiRankingMapper { * @param rankReqVO 参数 * @return 产品销量排行榜 */ - List selectProductSalesRank(CrmBiRankReqVO rankReqVO); + List selectProductSalesRank(CrmStatisticsRankReqVO rankReqVO); /** * 查询新增客户数排行榜 @@ -52,7 +52,7 @@ public interface CrmBiRankingMapper { * @param rankReqVO 参数 * @return 新增客户数排行榜 */ - List selectCustomerCountRank(CrmBiRankReqVO rankReqVO); + List selectCustomerCountRank(CrmStatisticsRankReqVO rankReqVO); /** * 查询联系人数量排行榜 @@ -60,7 +60,7 @@ public interface CrmBiRankingMapper { * @param rankReqVO 参数 * @return 联系人数量排行榜 */ - List selectContactsCountRank(CrmBiRankReqVO rankReqVO); + List selectContactsCountRank(CrmStatisticsRankReqVO rankReqVO); /** * 查询跟进次数排行榜 @@ -68,7 +68,7 @@ public interface CrmBiRankingMapper { * @param rankReqVO 参数 * @return 跟进次数排行榜 */ - List selectFollowCountRank(CrmBiRankReqVO rankReqVO); + List selectFollowCountRank(CrmStatisticsRankReqVO rankReqVO); /** * 查询跟进客户数排行榜 @@ -76,6 +76,6 @@ public interface CrmBiRankingMapper { * @param rankReqVO 参数 * @return 跟进客户数排行榜 */ - List selectFollowCustomerCountRank(CrmBiRankReqVO rankReqVO); + List selectFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/RedisKeyConstants.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/RedisKeyConstants.java new file mode 100644 index 000000000..2932c1db1 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/RedisKeyConstants.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.crm.dal.redis; + +/** + * CRM Redis Key 枚举类 + * + * @author 芋道源码 + */ +public interface RedisKeyConstants { + + /** + * 序号的缓存 + * + * KEY 格式:trade_no:{prefix} + * VALUE 数据格式:编号自增 + */ + String NO = "crm:seq_no:"; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/no/CrmNoRedisDAO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/no/CrmNoRedisDAO.java new file mode 100644 index 000000000..e917b13fd --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/redis/no/CrmNoRedisDAO.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.crm.dal.redis.no; + +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateUtil; +import cn.iocoder.yudao.module.crm.dal.redis.RedisKeyConstants; +import jakarta.annotation.Resource; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import java.time.Duration; +import java.time.LocalDateTime; + + +/** + * Crm 订单序号的 Redis DAO + * + * @author HUIHUI + */ +@Repository +public class CrmNoRedisDAO { + + /** + * 合同 {@link cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO} + */ + public static final String CONTRACT_NO_PREFIX = "HT"; + + /** + * 回款 {@link cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO} + */ + public static final String RECEIVABLE_PREFIX = "HK"; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * 生成序号,使用当前日期,格式为 {PREFIX} + yyyyMMdd + 6 位自增 + * 例如说:QTRK 202109 000001 (没有中间空格) + * + * @param prefix 前缀 + * @return 序号 + */ + public String generate(String prefix) { + // 递增序号 + String noPrefix = prefix + DateUtil.format(LocalDateTime.now(), DatePattern.PURE_DATE_PATTERN); + String key = RedisKeyConstants.NO + noPrefix; + Long no = stringRedisTemplate.opsForValue().increment(key); + // 设置过期时间 + stringRedisTemplate.expire(key, Duration.ofDays(1L)); + return noPrefix + String.format("%06d", no); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmReceivablePlanParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmReceivablePlanParseFunction.java new file mode 100644 index 000000000..b43074a8d --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmReceivablePlanParseFunction.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivablePlanService; +import com.mzt.logapi.service.IParseFunction; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * CRM 回款计划的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Component +@Slf4j +public class CrmReceivablePlanParseFunction implements IParseFunction { + + public static final String NAME = "getReceivablePlanServiceById"; + + @Resource + private CrmReceivablePlanService receivablePlanService; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(Long.parseLong(value.toString())); + return receivablePlan == null ? "" : receivablePlan.getPeriod().toString(); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmReceivableReturnTypeParseFunction.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmReceivableReturnTypeParseFunction.java new file mode 100644 index 000000000..2abdfe794 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/operatelog/core/CrmReceivableReturnTypeParseFunction.java @@ -0,0 +1,40 @@ +package cn.iocoder.yudao.module.crm.framework.operatelog.core; + +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.dict.core.util.DictFrameworkUtils; +import com.mzt.logapi.service.IParseFunction; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.CRM_RECEIVABLE_RETURN_TYPE; + +/** + * CRM 回款方式的 {@link IParseFunction} 实现类 + * + * @author HUIHUI + */ +@Slf4j +@Component +public class CrmReceivableReturnTypeParseFunction implements IParseFunction { + + public static final String NAME = "getReceivableReturnType"; + + @Override + public boolean executeBefore() { + return true; // 先转换值后对比 + } + + @Override + public String functionName() { + return NAME; + } + + @Override + public String apply(Object value) { + if (StrUtil.isEmptyIfStr(value)) { + return ""; + } + return DictFrameworkUtils.getDictDataLabel(CRM_RECEIVABLE_RETURN_TYPE, value.toString()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java index 14e7c71fe..292a4d664 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/aop/CrmPermissionAspect.java @@ -9,7 +9,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; -import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; +import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java deleted file mode 100644 index 43bb729b6..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -package cn.iocoder.yudao.module.crm.framework.permission.core.util; - -import cn.hutool.extra.spring.SpringUtil; -import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum; -import cn.iocoder.yudao.module.system.api.permission.PermissionApi; - -import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; - -/** - * 数据权限工具类 - * - * @author HUIHUI - */ -public class CrmPermissionUtils { - - /** - * 校验用户是否是 CRM 管理员 - * - * @return 是/否 - */ - public static boolean isCrmAdmin() { - return SingletonManager.getPermissionApi().hasAnyRoles(getLoginUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode()); - } - - /** - * 静态内部类实现单例获取 - * - * @author HUIHUI - */ - private static class SingletonManager { - - private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class); - - public static PermissionApi getPermissionApi() { - return PERMISSION_API; - } - - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java index 144f64d02..2214e6b95 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java @@ -1,4 +1,4 @@ /** - * TODO 芋艿:临时占位,后续可删除 + * 定时任务 */ package cn.iocoder.yudao.module.crm.job; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java index 683070d02..ab7982024 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessService.java @@ -4,15 +4,21 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateStatusReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.enums.business.CrmBusinessEndStatusEnum; import jakarta.validation.Valid; +import java.time.LocalDateTime; import java.util.Collection; import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * 商机 Service 接口 @@ -40,9 +46,26 @@ public interface CrmBusinessService { /** * 更新商机相关跟进信息 * - * @param updateFollowUpReqBOList 跟进信息 + * @param id 编号 + * @param contactNextTime 下次联系时间 + * @param contactLastContent 最后联系内容 */ - void updateBusinessFollowUpBatch(List updateFollowUpReqBOList); + void updateBusinessFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent); + + /** + * 更新商机的下次联系时间 + * + * @param ids 编号数组 + * @param contactNextTime 下次联系时间 + */ + void updateBusinessContactNextTime(Collection ids, LocalDateTime contactNextTime); + + /** + * 更新商机的状态 + * + * @param reqVO 更新请求 + */ + void updateBusinessStatus(CrmBusinessUpdateStatusReqVO reqVO); /** * 删除商机 @@ -59,13 +82,6 @@ public interface CrmBusinessService { */ void transferBusiness(CrmBusinessTransferReqVO reqVO, Long userId); - /** - * 更新商机关联商品 - * - * @param updateProductReqBO 请求 - */ - void updateBusinessProduct(CrmBusinessUpdateProductReqBO updateProductReqBO); - /** * 获得商机 * @@ -75,12 +91,12 @@ public interface CrmBusinessService { CrmBusinessDO getBusiness(Long id); /** - * 获得商机列表 + * 校验商机是否有效 * - * @param ids 编号 - * @return 商机列表 + * @param id 编号 + * @return 商机 */ - List getBusinessList(Collection ids, Long userId); + CrmBusinessDO validateBusiness(Long id); /** * 获得商机列表 @@ -90,6 +106,24 @@ public interface CrmBusinessService { */ List getBusinessList(Collection ids); + /** + * 获得商机 Map + * + * @param ids 编号 + * @return 商机 Map + */ + default Map getBusinessMap(Collection ids) { + return convertMap(getBusinessList(ids), CrmBusinessDO::getId); + } + + /** + * 获得指定商机编号的产品列表 + * + * @param businessId 商机编号 + * @return 商机产品列表 + */ + List getBusinessProductListByBusinessId(Long businessId); + /** * 获得商机分页 * @@ -129,4 +163,26 @@ public interface CrmBusinessService { */ Long getBusinessCountByCustomerId(Long customerId); + /** + * 获得使用指定商机状态组的商机数量 + * + * @param statusTypeId 商机状态组编号 + * @return 数量 + */ + Long getBusinessCountByStatusTypeId(Long statusTypeId); + + /** + * 获得商机状态名称 + * + * @param endStatus 结束状态 + * @param status 商机状态 + * @return 商机状态名称 + */ + default String getBusinessStatusName(Integer endStatus, CrmBusinessStatusDO status) { + if (endStatus != null) { + return CrmBusinessEndStatusEnum.fromStatus(endStatus).getName(); + } + return status.getName(); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java index 535578fd2..e709b6547 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java @@ -2,31 +2,32 @@ package cn.iocoder.yudao.module.crm.service.business; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessTransferReqVO; -import cn.iocoder.yudao.module.crm.convert.business.CrmBusinessConvert; +import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessUpdateStatusReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessMapper; import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessProductMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; -import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO; import cn.iocoder.yudao.module.crm.service.contact.CrmContactBusinessService; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; import cn.iocoder.yudao.module.crm.service.product.CrmProductService; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; @@ -36,10 +37,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.Collection; +import java.util.Collections; import java.util.List; -import java.util.Map; -import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; @@ -60,111 +62,117 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { @Resource private CrmBusinessProductMapper businessProductMapper; + @Resource + private CrmBusinessStatusService businessStatusService; @Resource @Lazy // 延迟加载,避免循环依赖 private CrmContractService contractService; @Resource + private CrmCustomerService customerService; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private CrmContactService contactService; + @Resource private CrmPermissionService permissionService; @Resource private CrmContactBusinessService contactBusinessService; @Resource private CrmProductService productService; + @Resource + private AdminUserApi adminUserApi; + @Override @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_CREATE_SUB_TYPE, bizNo = "{{#business.id}}", success = CRM_BUSINESS_CREATE_SUCCESS) public Long createBusiness(CrmBusinessSaveReqVO createReqVO, Long userId) { - createReqVO.setId(null); - // 1. 插入商机 - CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class).setOwnerUserId(userId); + // 1.1 校验产品项的有效性 + List businessProducts = validateBusinessProducts(createReqVO.getProducts()); + // 1.2 校验关联字段 + validateRelationDataExists(createReqVO); + + // 2.1 插入商机 + CrmBusinessDO business = BeanUtils.toBean(createReqVO, CrmBusinessDO.class); + business.setStatusId(businessStatusService.getBusinessStatusListByTypeId(createReqVO.getStatusTypeId()).get(0).getId()); // 默认状态 + calculateTotalPrice(business, businessProducts); businessMapper.insert(business); - // 1.2 插入商机关联商品 - if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话 - List productList = buildBusinessProductList(createReqVO.getProductItems(), business.getId()); - businessProductMapper.insertBatch(productList); - // 更新合同商品总金额 - businessMapper.updateById(new CrmBusinessDO().setId(business.getId()).setProductPrice( - getSumValue(productList, CrmBusinessProductDO::getTotalPrice, Integer::sum))); + // 2.2 插入商机关联商品 + if (CollUtil.isNotEmpty(businessProducts)) { + businessProducts.forEach(item -> item.setBusinessId(business.getId())); + businessProductMapper.insertBatch(businessProducts); } - // TODO @puhui999:在联系人的详情页,如果直接【新建商机】,则需要关联下。这里要搞个 CrmContactBusinessDO 表 - createContactBusiness(business.getId(), createReqVO.getContactId()); - // 2. 创建数据权限 - // 设置当前操作的人为负责人 - permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()) - .setBizId(business.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); + // 3. 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(business.getOwnerUserId()) + .setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType()).setBizId(business.getId()) + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); - // 3. 记录操作日志上下文 + // 4. 在联系人的详情页,如果直接【新建商机】,则需要关联下 + if (createReqVO.getContactId() != null) { + contactBusinessService.createContactBusinessList(new CrmContactBusinessReqVO().setContactId(createReqVO.getContactId()) + .setBusinessIds(Collections.singletonList(business.getId()))); + } + + // 5. 记录操作日志上下文 LogRecordContext.putVariable("business", business); return business.getId(); } - // TODO @lzxhqs:CrmContactBusinessService 调用这个;这样逻辑才能收敛哈; - private void createContactBusiness(Long businessId, Long contactId) { - CrmContactBusinessDO contactBusiness = new CrmContactBusinessDO(); - contactBusiness.setBusinessId(businessId); - contactBusiness.setContactId(contactId); - contactBusinessService.insert(contactBusiness); - } - @Override @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", success = CRM_BUSINESS_UPDATE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) public void updateBusiness(CrmBusinessSaveReqVO updateReqVO) { - // 1. 校验存在 + updateReqVO.setOwnerUserId(null).setStatusTypeId(null); // 不允许更新的字段 + // 1.1 校验存在 CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId()); + // 1.2 校验产品项的有效性 + List businessProducts = validateBusinessProducts(updateReqVO.getProducts()); + // 1.3 校验关联字段 + validateRelationDataExists(updateReqVO); // 2.1 更新商机 CrmBusinessDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessDO.class); + calculateTotalPrice(updateObj, businessProducts); businessMapper.updateById(updateObj); // 2.2 更新商机关联商品 - List productList = buildBusinessProductList(updateReqVO.getProductItems(), updateObj.getId()); - updateBusinessProduct(productList, updateObj.getId()); + updateBusinessProduct(updateObj.getId(), businessProducts); - // TODO @商机待定:如果状态发生变化,插入商机状态变更记录表 // 3. 记录操作日志上下文 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class)); LogRecordContext.putVariable("businessName", oldBusiness.getName()); } @Override - public void updateBusinessFollowUpBatch(List updateFollowUpReqBOList) { - businessMapper.updateBatch(CrmBusinessConvert.INSTANCE.convertList(updateFollowUpReqBOList)); - } - - @Override - @Transactional(rollbackFor = Exception.class) - @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_DELETE_SUB_TYPE, bizNo = "{{#id}}", - success = CRM_BUSINESS_DELETE_SUCCESS) - @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) - public void deleteBusiness(Long id) { - // 校验存在 + @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}", + success = CRM_BUSINESS_FOLLOW_UP_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) + public void updateBusinessFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) { + // 1. 校验存在 CrmBusinessDO business = validateBusinessExists(id); - // TODO @商机待定:需要校验有没关联合同。CrmContractDO 的 businessId 字段 - validateContractExists(id); - // 删除 - businessMapper.deleteById(id); - // 删除数据权限 - permissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id); + // 2. 更新联系人的跟进信息 + businessMapper.updateById(new CrmBusinessDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime) + .setContactLastTime(LocalDateTime.now())); - // 记录操作日志上下文 + // 3. 记录操作日志上下文 LogRecordContext.putVariable("businessName", business.getName()); } - private void updateBusinessProduct(List newProductList, Long businessId) { - List oldProducts = businessProductMapper.selectListByBusinessId(businessId); - List> diffList = CollectionUtils.diffList(oldProducts, newProductList, (oldValue, newValue) -> { - boolean condition = ObjectUtil.equal(oldValue.getProductId(), newValue.getProductId()); - if (condition) { - newValue.setId(oldValue.getId()); // 更新需要原始编号 - } - return condition; - }); + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#ids", level = CrmPermissionLevelEnum.WRITE) + public void updateBusinessContactNextTime(Collection ids, LocalDateTime contactNextTime) { + businessMapper.updateBatch(convertList(ids, id -> new CrmBusinessDO().setId(id).setContactNextTime(contactNextTime))); + } + + private void updateBusinessProduct(Long id, List newList) { + List oldList = businessProductMapper.selectListByBusinessId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setBusinessId(id)); businessProductMapper.insertBatch(diffList.get(0)); } if (CollUtil.isNotEmpty(diffList.get(1))) { @@ -175,22 +183,90 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { } } - private List buildBusinessProductList(List productItems, - Long businessId) { - // 校验商品存在 - Set productIds = convertSet(productItems, CrmBusinessSaveReqVO.CrmBusinessProductItem::getId); - List productList = productService.getProductList(productIds); - if (CollUtil.isEmpty(productIds) || productList.size() != productIds.size()) { - throw exception(PRODUCT_NOT_EXISTS); + private void validateRelationDataExists(CrmBusinessSaveReqVO saveReqVO) { + // 校验商机状态 + if (saveReqVO.getStatusTypeId() != null) { + businessStatusService.validateBusinessStatusType(saveReqVO.getStatusTypeId()); } - Map productMap = convertMap(productList, CrmProductDO::getId); - return convertList(productItems, productItem -> { - CrmProductDO product = productMap.get(productItem.getId()); - return BeanUtils.toBean(product, CrmBusinessProductDO.class) - .setId(null).setProductId(productItem.getId()).setBusinessId(businessId) - .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent()) - .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent())); - }); + // 校验客户 + if (saveReqVO.getCustomerId() != null) { + customerService.validateCustomer(saveReqVO.getCustomerId()); + } + // 校验联系人 + if (saveReqVO.getContactId() != null) { + contactService.validateContact(saveReqVO.getContactId()); + } + // 校验负责人 + if (saveReqVO.getOwnerUserId() != null) { + adminUserApi.validateUser(saveReqVO.getOwnerUserId()); + } + } + + private List validateBusinessProducts(List list) { + // 1. 校验产品存在 + productService.validProductList(convertSet(list, CrmBusinessSaveReqVO.Product::getProductId)); + // 2. 转化为 CrmBusinessProductDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class, + item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount())))); + } + + private void calculateTotalPrice(CrmBusinessDO business, List businessProducts) { + business.setTotalProductPrice(getSumValue(businessProducts, CrmBusinessProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)); + BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(business.getTotalProductPrice(), business.getDiscountPercent()); + business.setTotalPrice(business.getTotalProductPrice().subtract(discountPrice)); + } + + @Override + @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_UPDATE_STATUS_SUB_TYPE, bizNo = "{{#reqVO.id}}", + success = CRM_BUSINESS_UPDATE_STATUS_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.WRITE) + public void updateBusinessStatus(CrmBusinessUpdateStatusReqVO reqVO) { + // 1.1 校验存在 + CrmBusinessDO business = validateBusinessExists(reqVO.getId()); + // 1.2 校验商机未结束 + if (business.getEndStatus() != null) { + throw exception(BUSINESS_UPDATE_STATUS_FAIL_END_STATUS); + } + // 1.3 校验商机状态 + CrmBusinessStatusDO status = null; + if (reqVO.getStatusId() != null) { + status = businessStatusService.validateBusinessStatus(business.getStatusTypeId(), reqVO.getStatusId()); + } + // 1.4 校验是不是状态没变更 + if ((reqVO.getStatusId() != null && reqVO.getStatusId().equals(business.getStatusId())) + || (reqVO.getEndStatus() != null && reqVO.getEndStatus().equals(business.getEndStatus()))) { + throw exception(BUSINESS_UPDATE_STATUS_FAIL_STATUS_EQUALS); + } + + // 2. 更新商机状态 + businessMapper.updateById(new CrmBusinessDO().setId(reqVO.getId()).setStatusId(reqVO.getStatusId()) + .setEndStatus(reqVO.getEndStatus())); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("businessName", business.getName()); + LogRecordContext.putVariable("oldStatusName", getBusinessStatusName(business.getEndStatus(), + businessStatusService.getBusinessStatus(business.getStatusId()))); + LogRecordContext.putVariable("newStatusName", getBusinessStatusName(reqVO.getEndStatus(), status)); + } + + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_BUSINESS_TYPE, subType = CRM_BUSINESS_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_BUSINESS_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void deleteBusiness(Long id) { + // 1.1 校验存在 + CrmBusinessDO business = validateBusinessExists(id); + // 1.2 校验是否关联合同 + validateContractExists(id); + + // 删除商机 + businessMapper.deleteById(id); + // 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_BUSINESS.getType(), id); + + // 记录操作日志上下文 + LogRecordContext.putVariable("businessName", business.getName()); } /** @@ -201,7 +277,7 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { */ private void validateContractExists(Long businessId) { if (contractService.getContractCountByBusinessId(businessId) > 0) { - throw exception(BUSINESS_CONTRACT_EXISTS); + throw exception(BUSINESS_DELETE_FAIL_CONTRACT_EXISTS); } } @@ -224,8 +300,8 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { CrmBusinessDO business = validateBusinessExists(reqVO.getId()); // 2.1 数据权限转移 - permissionService.transferPermission( - CrmBusinessConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_BUSINESS.getType())); + permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_BUSINESS.getType(), + reqVO.getNewOwnerUserId(), reqVO.getId(), CrmPermissionLevelEnum.OWNER.getLevel())); // 2.2 设置新的负责人 businessMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); @@ -233,14 +309,6 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { LogRecordContext.putVariable("business", business); } - @Override - public void updateBusinessProduct(CrmBusinessUpdateProductReqBO updateProductReqBO) { - // 更新商机关联商品 - List productList = buildBusinessProductList( - BeanUtils.toBean(updateProductReqBO.getProductItems(), CrmBusinessSaveReqVO.CrmBusinessProductItem.class), updateProductReqBO.getId()); - updateBusinessProduct(productList, updateProductReqBO.getId()); - } - //======================= 查询相关 ======================= @Override @@ -250,11 +318,8 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { } @Override - public List getBusinessList(Collection ids, Long userId) { - if (CollUtil.isEmpty(ids)) { - return ListUtil.empty(); - } - return businessMapper.selectBatchIds(ids, userId); + public CrmBusinessDO validateBusiness(Long id) { + return validateBusinessExists(id); } @Override @@ -265,6 +330,11 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { return businessMapper.selectBatchIds(ids); } + @Override + public List getBusinessProductListByBusinessId(Long businessId) { + return businessProductMapper.selectListByBusinessId(businessId); + } + @Override public PageResult getBusinessPage(CrmBusinessPageReqVO pageReqVO, Long userId) { return businessMapper.selectPage(pageReqVO, userId); @@ -295,4 +365,9 @@ public class CrmBusinessServiceImpl implements CrmBusinessService { return businessMapper.selectCount(CrmBusinessDO::getCustomerId, customerId); } + @Override + public Long getBusinessCountByStatusTypeId(Long statusTypeId) { + return businessMapper.selectCountByStatusTypeId(statusTypeId); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java index a2fc2d18d..bd9c16439 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusService.java @@ -1,15 +1,17 @@ package cn.iocoder.yudao.module.crm.service.business; +import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; - +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; import jakarta.validation.Valid; import java.util.Collection; import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * 商机状态 Service 接口 @@ -38,7 +40,81 @@ public interface CrmBusinessStatusService { * * @param id 编号 */ - void deleteBusinessStatus(Long id); + void deleteBusinessStatusType(Long id); + + /** + * 获得商机状态组 + * + * @param id 编号 + * @return 商机状态组 + */ + CrmBusinessStatusTypeDO getBusinessStatusType(Long id); + + /** + * 校验商机状态组 + * + * @param id 编号 + */ + void validateBusinessStatusType(Long id); + + /** + * 获得商机状态组列表 + * + * @return 商机状态组列表 + */ + List getBusinessStatusTypeList(); + + /** + * 获得商机状态组分页 + * + * @param pageReqVO 分页查询 + * @return 商机状态组分页 + */ + PageResult getBusinessStatusTypePage(PageParam pageReqVO); + + /** + * 获得商机状态组列表 + * + * @param ids 编号数组 + * @return 商机状态组列表 + */ + List getBusinessStatusTypeList(Collection ids); + + /** + * 获得商机状态组 Map + * + * @param ids 编号数组 + * @return 商机状态组 Map + */ + default Map getBusinessStatusTypeMap(Collection ids) { + return convertMap(getBusinessStatusTypeList(ids), CrmBusinessStatusTypeDO::getId); + } + + /** + * 获得指定类型的商机状态列表 + * + * @param typeId 商机状态组编号 + * @return 商机状态列表 + */ + List getBusinessStatusListByTypeId(Long typeId); + + /** + * 获得商机状态列表 + * + * @param ids 编号数组 + * @return 商机状态列表 + */ + List getBusinessStatusList(Collection ids); + + /** + * 获得商机状态 Map + * + * @param ids 编号数组 + * @return 商机状态 Map + */ + default Map getBusinessStatusMap(Collection ids) { + return convertMap(getBusinessStatusList(ids), CrmBusinessStatusDO::getId); + } /** * 获得商机状态 @@ -49,29 +125,11 @@ public interface CrmBusinessStatusService { CrmBusinessStatusDO getBusinessStatus(Long id); /** - * 获得商机状态分页 + * 校验商机状态 * - * @param pageReqVO 分页查询 - * @return 商机状态分页 + * @param statusTypeId 商机状态组编号 + * @param statusId 商机状态编号 */ - PageResult getBusinessStatusPage(CrmBusinessStatusPageReqVO pageReqVO); - - // TODO @ljlleo 常用的 ids 之类的查询,可以封装单独的方法,不用走类似 QueryVO,用起来更方便。 - // TODO @ljlleo 方法名用 getBusinessStatusList - /** - * 获得商机状态分页 - * - * @param queryVO 查询参数 - * @return 商机状态分页 - */ - List selectList(CrmBusinessStatusQueryVO queryVO); - - /** - * 获得商机状态列表 - * - * @param ids 编号数组 - * @return 商机状态列表 - */ - List getBusinessStatusList(Collection ids); + CrmBusinessStatusDO validateBusinessStatus(Long statusTypeId, Long statusId); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java index 2e49e99d7..2ec77667a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusServiceImpl.java @@ -1,22 +1,29 @@ package cn.iocoder.yudao.module.crm.service.business; +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.common.pojo.PageParam; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusQueryVO; import cn.iocoder.yudao.module.crm.controller.admin.business.vo.status.CrmBusinessStatusSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusMapper; +import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusTypeMapper; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; - import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_NOT_EXISTS; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.diffList; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; /** * 商机状态 Service 实现类 @@ -27,40 +34,148 @@ import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STAT @Validated public class CrmBusinessStatusServiceImpl implements CrmBusinessStatusService { + @Resource + private CrmBusinessStatusTypeMapper businessStatusTypeMapper; @Resource private CrmBusinessStatusMapper businessStatusMapper; + @Resource + @Lazy // 延迟加载,避免循环依赖 + private CrmBusinessService businessService; + @Override + @Transactional(rollbackFor = Exception.class) public Long createBusinessStatus(CrmBusinessStatusSaveReqVO createReqVO) { - // 插入 - CrmBusinessStatusDO businessStatus = BeanUtils.toBean(createReqVO, CrmBusinessStatusDO.class); - businessStatusMapper.insert(businessStatus); - // 返回 - return businessStatus.getId(); - } - - @Override - public void updateBusinessStatus(CrmBusinessStatusSaveReqVO updateReqVO) { - // 校验存在 - validateBusinessStatusExists(updateReqVO.getId()); - // 更新 - CrmBusinessStatusDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessStatusDO.class); - businessStatusMapper.updateById(updateObj); - } - - @Override - public void deleteBusinessStatus(Long id) { - // 校验存在 - validateBusinessStatusExists(id); - // TODO @ljlleo 这里可以考虑,如果有商机在使用,不允许删除 - // 删除 - businessStatusMapper.deleteById(id); - } - - private void validateBusinessStatusExists(Long id) { - if (businessStatusMapper.selectById(id) == null) { - throw exception(BUSINESS_STATUS_NOT_EXISTS); + // 1.1 检验名称是否存在 + validateBusinessStatusTypeNameUnique(createReqVO.getName(), null); + // 1.2 设置状态的排序 + int sort = 0; + for (CrmBusinessStatusSaveReqVO.Status status : createReqVO.getStatuses()) { + status.setSort(sort++); } + + // 2.1 插入类型 + CrmBusinessStatusTypeDO statusType = BeanUtils.toBean(createReqVO, CrmBusinessStatusTypeDO.class); + businessStatusTypeMapper.insert(statusType); + // 2.2 插入状态 + List statuses = BeanUtils.toBean(createReqVO.getStatuses(), CrmBusinessStatusDO.class, + status -> status.setTypeId(statusType.getId())); + businessStatusMapper.insertBatch(statuses); + return statusType.getId(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void updateBusinessStatus(CrmBusinessStatusSaveReqVO updateReqVO) { + // 1.1 校验存在 + validateBusinessStatusTypeExists(updateReqVO.getId()); + // 1.2 校验名称是否存在 + validateBusinessStatusTypeNameUnique(updateReqVO.getName(), updateReqVO.getId()); + // 1.3 设置状态的排序 + int sort = 0; + for (CrmBusinessStatusSaveReqVO.Status status : updateReqVO.getStatuses()) { + status.setSort(sort++); + } + // 1.4 已经使用,无法更新 + if (businessService.getBusinessCountByStatusTypeId(updateReqVO.getId()) > 0) { + throw exception(BUSINESS_STATUS_UPDATE_FAIL_USED); + } + + // 2.1 更新类型 + CrmBusinessStatusTypeDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessStatusTypeDO.class); + businessStatusTypeMapper.updateById(updateObj); + // 2.2 更新状态 + updateBusinessStatus(updateReqVO.getId(), BeanUtils.toBean(updateReqVO.getStatuses(), CrmBusinessStatusDO.class)); + } + + private void updateBusinessStatus(Long id, List newList) { + List oldList = businessStatusMapper.selectListByTypeId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); + if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setTypeId(id)); + businessStatusMapper.insertBatch(diffList.get(0)); + } + if (CollUtil.isNotEmpty(diffList.get(1))) { + businessStatusMapper.updateBatch(diffList.get(1)); + } + if (CollUtil.isNotEmpty(diffList.get(2))) { + businessStatusMapper.deleteBatchIds(convertSet(diffList.get(2), CrmBusinessStatusDO::getId)); + } + } + + private void validateBusinessStatusTypeExists(Long id) { + if (businessStatusTypeMapper.selectById(id) == null) { + throw exception(BUSINESS_STATUS_TYPE_NOT_EXISTS); + } + } + + private void validateBusinessStatusTypeNameUnique(String name, Long id) { + CrmBusinessStatusTypeDO statusType = businessStatusTypeMapper.selectByName(name); + if (statusType == null + || statusType.getId().equals(id)) { + return; + } + throw exception(BUSINESS_STATUS_TYPE_NAME_EXISTS); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void deleteBusinessStatusType(Long id) { + // 1.1 校验存在 + validateBusinessStatusTypeExists(id); + // 1.2 已经使用,无法更新 + if (businessService.getBusinessCountByStatusTypeId(id) > 0) { + throw exception(BUSINESS_STATUS_DELETE_FAIL_USED); + } + + // 2.1 删除类型 + businessStatusTypeMapper.deleteById(id); + // 2.2 删除状态 + businessStatusMapper.deleteByTypeId(id); + } + + @Override + public CrmBusinessStatusTypeDO getBusinessStatusType(Long id) { + return businessStatusTypeMapper.selectById(id); + } + + @Override + public void validateBusinessStatusType(Long id) { + validateBusinessStatusTypeExists(id); + } + + @Override + public List getBusinessStatusTypeList() { + return businessStatusTypeMapper.selectList(); + } + + @Override + public PageResult getBusinessStatusTypePage(PageParam pageReqVO) { + return businessStatusTypeMapper.selectPage(pageReqVO); + } + + @Override + public List getBusinessStatusTypeList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return businessStatusTypeMapper.selectBatchIds(ids); + } + + @Override + public List getBusinessStatusListByTypeId(Long typeId) { + List list = businessStatusMapper.selectListByTypeId(typeId); + list.sort(Comparator.comparingInt(CrmBusinessStatusDO::getSort)); + return list; + } + + @Override + public List getBusinessStatusList(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyList(); + } + return businessStatusMapper.selectBatchIds(ids); } @Override @@ -69,18 +184,12 @@ public class CrmBusinessStatusServiceImpl implements CrmBusinessStatusService { } @Override - public PageResult getBusinessStatusPage(CrmBusinessStatusPageReqVO pageReqVO) { - return businessStatusMapper.selectPage(pageReqVO); - } - - @Override - public List selectList(CrmBusinessStatusQueryVO queryVO) { - return businessStatusMapper.selectList(queryVO); - } - - @Override - public List getBusinessStatusList(Collection ids) { - return businessStatusMapper.selectBatchIds(ids); + public CrmBusinessStatusDO validateBusinessStatus(Long statusTypeId, Long statusId) { + CrmBusinessStatusDO status = businessStatusMapper.selectByTypeIdAndId(statusTypeId, statusId); + if (status == null) { + throw exception(BUSINESS_STATUS_NOT_EXISTS); + } + return status; } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java deleted file mode 100644 index 20509994e..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeService.java +++ /dev/null @@ -1,75 +0,0 @@ -package cn.iocoder.yudao.module.crm.service.business; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; -import jakarta.validation.Valid; - -import java.util.Collection; -import java.util.List; - -/** - * 商机状态类型 Service 接口 - * - * @author ljlleo - */ -public interface CrmBusinessStatusTypeService { - - /** - * 创建商机状态类型 - * - * @param createReqVO 创建信息 - * @return 编号 - */ - Long createBusinessStatusType(@Valid CrmBusinessStatusTypeSaveReqVO createReqVO); - - /** - * 更新商机状态类型 - * - * @param updateReqVO 更新信息 - */ - void updateBusinessStatusType(@Valid CrmBusinessStatusTypeSaveReqVO updateReqVO); - - /** - * 删除商机状态类型 - * - * @param id 编号 - */ - void deleteBusinessStatusType(Long id); - - /** - * 获得商机状态类型 - * - * @param id 编号 - * @return 商机状态类型 - */ - CrmBusinessStatusTypeDO getBusinessStatusType(Long id); - - /** - * 获得商机状态类型分页 - * - * @param pageReqVO 分页查询 - * @return 商机状态类型分页 - */ - PageResult getBusinessStatusTypePage(CrmBusinessStatusTypePageReqVO pageReqVO); - - // TODO @ljlleo 常用的 ids 之类的查询,可以封装单独的方法,不用走类似 QueryVO,用起来更方便。 - /** - * 获得商机状态类型列表 - * - * @param queryVO 查询参数 - * @return 商机状态类型列表 - */ - List selectList(CrmBusinessStatusTypeQueryVO queryVO); - - /** - * 获得商机状态类型列表 - * - * @param ids 编号数组 - * @return 商机状态类型列表 - */ - List getBusinessStatusTypeList(Collection ids); - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java deleted file mode 100644 index d9845976b..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessStatusTypeServiceImpl.java +++ /dev/null @@ -1,132 +0,0 @@ -package cn.iocoder.yudao.module.crm.service.business; - -import cn.hutool.core.collection.CollUtil; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypePageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeQueryVO; -import cn.iocoder.yudao.module.crm.controller.admin.business.vo.type.CrmBusinessStatusTypeSaveReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessStatusTypeDO; -import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusMapper; -import cn.iocoder.yudao.module.crm.dal.mysql.business.CrmBusinessStatusTypeMapper; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.validation.annotation.Validated; - -import jakarta.annotation.Resource; - -import java.util.Collection; -import java.util.List; - -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_TYPE_NOT_EXISTS; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_STATUS_TYPE_NAME_EXISTS; - -/** - * 商机状态类型 Service 实现类 - * - * @author ljlleo - */ -@Service -@Validated -public class CrmBusinessStatusTypeServiceImpl implements CrmBusinessStatusTypeService { - - @Resource - private CrmBusinessStatusTypeMapper businessStatusTypeMapper; - - @Resource - private CrmBusinessStatusMapper businessStatusMapper; - - @Override - @Transactional(rollbackFor = Exception.class) - public Long createBusinessStatusType(CrmBusinessStatusTypeSaveReqVO createReqVO) { - //检验名称是否存在 - validateBusinessStatusTypeExists(createReqVO.getName(), null); - // 插入类型 - CrmBusinessStatusTypeDO businessStatusType = BeanUtils.toBean(createReqVO, CrmBusinessStatusTypeDO.class); - businessStatusTypeMapper.insert(businessStatusType); - // 插入状态 - if (CollUtil.isNotEmpty(createReqVO.getStatusList())) { - createReqVO.getStatusList().forEach(status -> status.setTypeId(businessStatusType.getId())); - businessStatusMapper.insertBatch(BeanUtils.toBean(createReqVO.getStatusList(), CrmBusinessStatusDO.class)); - } - return businessStatusType.getId(); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void updateBusinessStatusType(CrmBusinessStatusTypeSaveReqVO updateReqVO) { - // 校验存在 - validateBusinessStatusTypeExists(updateReqVO.getId()); - // 校验名称是否存在 - validateBusinessStatusTypeExists(updateReqVO.getName(), updateReqVO.getId()); - // 更新类型 - CrmBusinessStatusTypeDO updateObj = BeanUtils.toBean(updateReqVO, CrmBusinessStatusTypeDO.class); - businessStatusTypeMapper.updateById(updateObj); - // 更新状态(删除 + 更新) - // TODO @ljlleo 可以参考 DeliveryExpressTemplateServiceImpl 的 updateExpressTemplateFree 方法;主要没变化的,还是不删除了哈。 - businessStatusMapper.delete(updateReqVO.getId()); - updateReqVO.getStatusList().forEach(status -> status.setTypeId(updateReqVO.getId())); - businessStatusMapper.insertBatch(BeanUtils.toBean(updateReqVO.getStatusList(), CrmBusinessStatusDO.class)); - } - - @Override - @Transactional(rollbackFor = Exception.class) - public void deleteBusinessStatusType(Long id) { - // TODO 待添加被引用校验 - //... - - // 校验存在 - validateBusinessStatusTypeExists(id); - // 删除类型 - businessStatusTypeMapper.deleteById(id); - // 删除状态 - businessStatusMapper.delete(id); - } - - private void validateBusinessStatusTypeExists(Long id) { - if (businessStatusTypeMapper.selectById(id) == null) { - throw exception(BUSINESS_STATUS_TYPE_NOT_EXISTS); - } - } - - // TODO @ljlleo 这个方法,这个参考 validateDeptNameUnique 实现。 - private void validateBusinessStatusTypeExists(String name, Long id) { - CrmBusinessStatusTypeDO businessStatusTypeDO = businessStatusTypeMapper.selectByIdAndName(id, name); - if (businessStatusTypeDO != null) { - throw exception(BUSINESS_STATUS_TYPE_NAME_EXISTS); - } -// LambdaQueryWrapper wrapper = new LambdaQueryWrapperX<>(); -// if(null != id) { -// wrapper.ne(CrmBusinessStatusTypeDO::getId, id); -// } -// long cnt = businessStatusTypeMapper.selectCount(wrapper.eq(CrmBusinessStatusTypeDO::getName, name)); -// if (cnt > 0) { -// throw exception(BUSINESS_STATUS_TYPE_NAME_EXISTS); -// } - } - - @Override - public CrmBusinessStatusTypeDO getBusinessStatusType(Long id) { - return businessStatusTypeMapper.selectById(id); - } - - @Override - public PageResult getBusinessStatusTypePage(CrmBusinessStatusTypePageReqVO pageReqVO) { - return businessStatusTypeMapper.selectPage(pageReqVO); - } - - @Override - public List selectList(CrmBusinessStatusTypeQueryVO queryVO) { - return businessStatusTypeMapper.selectList(queryVO); - } - - @Override - public List getBusinessStatusTypeList(Collection ids) { - return businessStatusTypeMapper.selectBatchIds(ids); - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateProductReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateProductReqBO.java deleted file mode 100644 index 34b2fa381..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/business/bo/CrmBusinessUpdateProductReqBO.java +++ /dev/null @@ -1,49 +0,0 @@ -package cn.iocoder.yudao.module.crm.service.business.bo; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; - -/** - * 更新商机商品 Update Req BO - * - * @author HUIHUI - */ -@Data -public class CrmBusinessUpdateProductReqBO { - - /** - * 商机编号 - */ - @NotNull(message = "商机编号不能为空") - private Long id; - - // TODO @芋艿:再想想 - @NotEmpty(message = "产品列表不能为空") - private List productItems; - - @Schema(description = "产品列表") - @Data - @NoArgsConstructor - @AllArgsConstructor - public static class CrmBusinessProductItem { - - @Schema(description = "产品编号", example = "20529") - @NotNull(message = "产品编号不能为空") - private Long id; - - @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911") - @NotNull(message = "产品数量不能为空") - private Integer count; - - @Schema(description = "产品折扣") - private Integer discountPercent; - - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java index 1472bf02f..b84c6d51c 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueService.java @@ -4,11 +4,10 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTranslateReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; import jakarta.validation.Valid; +import java.time.LocalDateTime; import java.util.Collection; import java.util.List; @@ -23,10 +22,9 @@ public interface CrmClueService { * 创建线索 * * @param createReqVO 创建信息 - * @param userId 用户编号 * @return 编号 */ - Long createClue(@Valid CrmClueSaveReqVO createReqVO, Long userId); + Long createClue(@Valid CrmClueSaveReqVO createReqVO); /** * 更新线索 @@ -38,9 +36,11 @@ public interface CrmClueService { /** * 更新线索相关的跟进信息 * - * @param clueUpdateFollowUpReqBO 信息 + * @param id 编号 + * @param contactNextTime 下次联系时间 + * @param contactLastContent 最后联系内容 */ - void updateClueFollowUp(CrmUpdateFollowUpReqBO clueUpdateFollowUpReqBO); + void updateClueFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent); /** * 删除线索 @@ -85,9 +85,17 @@ public interface CrmClueService { /** * 线索转化为客户 * - * @param reqVO 线索编号 + * @param id 线索编号 * @param userId 用户编号 */ - void translateCustomer(CrmClueTranslateReqVO reqVO, Long userId); + void transformClue(Long id, Long userId); + + /** + * 获得分配给我的、待跟进的线索数量 + * + * @param userId 用户编号 + * @return 数量 + */ + Long getFollowClueCount(Long userId); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java index dfb044f50..70ebeb44d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java @@ -3,16 +3,12 @@ package cn.iocoder.yudao.module.crm.service.clue; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ObjectUtil; -import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmCluePageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransferReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTranslateReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; -import cn.iocoder.yudao.module.crm.convert.clue.CrmClueConvert; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.CrmCustomerSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper; @@ -23,9 +19,9 @@ import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService; import cn.iocoder.yudao.module.crm.service.followup.bo.CrmFollowUpCreateReqBO; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; @@ -35,14 +31,18 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import java.util.*; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.List; +import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.singleton; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CLUE_NOT_EXISTS; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CLUE_TRANSFORM_FAIL_ALREADY; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS; -import static java.util.Collections.singletonList; /** * 线索 Service 实现类 @@ -58,34 +58,30 @@ public class CrmClueServiceImpl implements CrmClueService { @Resource private CrmCustomerService customerService; - @Resource private CrmPermissionService crmPermissionService; @Resource private CrmFollowUpRecordService followUpRecordService; + @Resource private AdminUserApi adminUserApi; @Override @Transactional(rollbackFor = Exception.class) - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_CREATE_SUB_TYPE, bizNo = "{{#clue.id}}", - success = CRM_LEADS_CREATE_SUCCESS) - public Long createClue(CrmClueSaveReqVO createReqVO, Long userId) { + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_CREATE_SUB_TYPE, bizNo = "{{#clue.id}}", + success = CRM_CLUE_CREATE_SUCCESS) + public Long createClue(CrmClueSaveReqVO createReqVO) { // 1.1 校验关联数据 validateRelationDataExists(createReqVO); // 1.2 校验负责人是否存在 - if (createReqVO.getOwnerUserId() != null) { - adminUserApi.validateUserList(singletonList(createReqVO.getOwnerUserId())); - } else { - createReqVO.setOwnerUserId(userId); // 如果没有设置负责人那么默认操作人为负责人 - } + adminUserApi.validateUser(createReqVO.getOwnerUserId()); - // 2. 插入 - CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class).setId(null); + // 2. 插入线索 + CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class); clueMapper.insert(clue); // 3. 创建数据权限 - CrmPermissionCreateReqBO createReqBO = new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_LEADS.getType()) + CrmPermissionCreateReqBO createReqBO = new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CLUE.getType()) .setBizId(clue.getId()).setUserId(clue.getOwnerUserId()).setLevel(CrmPermissionLevelEnum.OWNER.getLevel()); crmPermissionService.createPermission(createReqBO); @@ -96,17 +92,17 @@ public class CrmClueServiceImpl implements CrmClueService { @Override @Transactional(rollbackFor = Exception.class) - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", - success = CRM_LEADS_UPDATE_SUCCESS) - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#updateReq.id", level = CrmPermissionLevelEnum.OWNER) + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", + success = CRM_CLUE_UPDATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#updateReq.id", level = CrmPermissionLevelEnum.OWNER) public void updateClue(CrmClueSaveReqVO updateReq) { Assert.notNull(updateReq.getId(), "线索编号不能为空"); - // 1. 校验线索是否存在 + // 1.1 校验线索是否存在 CrmClueDO oldClue = validateClueExists(updateReq.getId()); - // 2. 校验关联数据 + // 1.2 校验关联数据 validateRelationDataExists(updateReq); - // 3. 更新 + // 2. 更新线索 CrmClueDO updateObj = BeanUtils.toBean(updateReq, CrmClueDO.class); clueMapper.updateById(updateObj); @@ -115,27 +111,35 @@ public class CrmClueServiceImpl implements CrmClueService { LogRecordContext.putVariable("clueName", oldClue.getName()); } - @Override - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_UPDATE_SUB_TYPE, bizNo = "{{#updateReq.bizId}", - success = CRM_LEADS_UPDATE_SUCCESS) - public void updateClueFollowUp(CrmUpdateFollowUpReqBO updateReq) { - // 校验线索是否存在 - CrmClueDO oldClue = validateClueExists(updateReq.getBizId()); + private void validateRelationDataExists(CrmClueSaveReqVO reqVO) { + // 校验负责人 + if (Objects.nonNull(reqVO.getOwnerUserId()) && + Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) { + throw exception(USER_NOT_EXISTS); + } + } - // 更新 - clueMapper.updateById(BeanUtils.toBean(updateReq, CrmClueDO.class).setId(updateReq.getBizId())); + @Override + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}", + success = CRM_CLUE_FOLLOW_UP_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) + public void updateClueFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) { + // 校验线索是否存在 + CrmClueDO oldClue = validateClueExists(id); + + // 更新线索 + clueMapper.updateById(new CrmClueDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime) + .setContactLastTime(LocalDateTime.now()).setContactLastContent(contactLastContent)); // 3. 记录操作日志上下文 - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldClue, CrmUpdateFollowUpReqBO.class)); LogRecordContext.putVariable("clueName", oldClue.getName()); - } @Override @Transactional(rollbackFor = Exception.class) - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_DELETE_SUB_TYPE, bizNo = "{{#id}}", - success = CRM_LEADS_DELETE_SUCCESS) - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_DELETE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CLUE_DELETE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteClue(Long id) { // 1. 校验存在 CrmClueDO clue = validateClueExists(id); @@ -144,29 +148,29 @@ public class CrmClueServiceImpl implements CrmClueService { clueMapper.deleteById(id); // 3. 删除数据权限 - crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_LEADS.getType(), id); + crmPermissionService.deletePermission(CrmBizTypeEnum.CRM_CLUE.getType(), id); // 4. 删除跟进 - followUpRecordService.deleteFollowUpRecordByBiz(CrmBizTypeEnum.CRM_LEADS.getType(), id); + followUpRecordService.deleteFollowUpRecordByBiz(CrmBizTypeEnum.CRM_CLUE.getType(), id); - // 记录操作日志上下文 + // 5. 记录操作日志上下文 LogRecordContext.putVariable("clueName", clue.getName()); } - @Override @Transactional(rollbackFor = Exception.class) - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", - success = CRM_LEADS_TRANSFER_SUCCESS) - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", + success = CRM_CLUE_TRANSFER_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#reqVO.id", level = CrmPermissionLevelEnum.OWNER) public void transferClue(CrmClueTransferReqVO reqVO, Long userId) { // 1 校验线索是否存在 CrmClueDO clue = validateClueExists(reqVO.getId()); // 2.1 数据权限转移 - crmPermissionService.transferPermission(CrmClueConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_LEADS.getType())); + crmPermissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CLUE.getType(), + reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel())); // 2.2 设置新的负责人 - clueMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + clueMapper.updateById(new CrmClueDO().setId(reqVO.getId()).setOwnerUserId(reqVO.getNewOwnerUserId())); // 3. 记录转移日志 LogRecordContext.putVariable("clue", clue); @@ -174,70 +178,32 @@ public class CrmClueServiceImpl implements CrmClueService { @Override @Transactional(rollbackFor = Exception.class) - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) - public void translateCustomer(CrmClueTranslateReqVO reqVO, Long userId) { + @LogRecord(type = CRM_CLUE_TYPE, subType = CRM_CLUE_TRANSLATE_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CLUE_TRANSLATE_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) + public void transformClue(Long id, Long userId) { // 1.1 校验线索都存在 - Set clueIds = reqVO.getIds(); - List clues = getClueList(clueIds, userId); - if (CollUtil.isEmpty(clues) || ObjectUtil.notEqual(clues.size(), clueIds.size())) { - clueIds.removeAll(convertSet(clues, CrmClueDO::getId)); - throw exception(CLUE_ANY_CLUE_NOT_EXISTS, clueIds); - } - // 1.2 存在已经转化的,直接提示哈。避免操作的用户,以为都转化成功了 - List translatedClues = filterList(clues, - clue -> ObjectUtil.equal(Boolean.TRUE, clue.getTransformStatus())); - if (CollUtil.isNotEmpty(translatedClues)) { - throw exception(CLUE_ANY_CLUE_ALREADY_TRANSLATED, convertSet(translatedClues, CrmClueDO::getId)); + CrmClueDO clue = validateClueExists(id); + // 1.2 存在已经转化的 + if (clue.getTransformStatus()) { + throw exception(CLUE_TRANSFORM_FAIL_ALREADY); } // 2.1 遍历线索(未转化的线索),创建对应的客户 - clues.forEach(clue -> { - Long customerId = customerService.createCustomer(BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class), userId); - clue.setCustomerId(customerId); - }); + Long customerId = customerService.createCustomer(BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class), userId); // 2.2 更新线索 - clueMapper.updateBatch(convertList(clues, clue -> new CrmClueDO().setId(clue.getId()) - .setTransformStatus(Boolean.TRUE).setCustomerId(clue.getCustomerId()))); + clueMapper.updateById(new CrmClueDO().setId(id).setTransformStatus(Boolean.TRUE).setCustomerId(customerId)); // 2.3 复制跟进记录 - copyFollowUpRecords(clues); - - // 3. 记录操作日志 - for (CrmClueDO clue : clues) { - getSelf().translateCustomerLog(clue); - } - } - - /** - * 线索被转换客户后,需要将线索的跟进记录,复制到客户上 - * - * @param clues 被转化的线索 - */ - private void copyFollowUpRecords(List clues) { List followUpRecords = followUpRecordService.getFollowUpRecordByBiz( - CrmBizTypeEnum.CRM_LEADS.getType(), convertSet(clues, CrmClueDO::getId)); - if (CollUtil.isEmpty(followUpRecords)) { - return; + CrmBizTypeEnum.CRM_CLUE.getType(), singleton(clue.getId())); + if (CollUtil.isNotEmpty(followUpRecords)) { + followUpRecordService.createFollowUpRecordBatch(convertList(followUpRecords, record -> + BeanUtils.toBean(record, CrmFollowUpCreateReqBO.class) + .setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()).setBizId(customerId))); } - // 创建跟进 - Map clueMap = convertMap(clues, CrmClueDO::getId); - followUpRecordService.createFollowUpRecordBatch(convertList(followUpRecords, followUpRecord -> - BeanUtils.toBean(followUpRecord, CrmFollowUpCreateReqBO.class).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) - .setBizId(clueMap.get(followUpRecord.getBizId()).getCustomerId()))); - } - @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSLATE_SUB_TYPE, bizNo = "{{#clue.id}}", - success = CRM_LEADS_TRANSLATE_SUCCESS) - public void translateCustomerLog(CrmClueDO clue) { - // 记录操作日志上下文 - LogRecordContext.putVariable("clue", clue); - } - - private void validateRelationDataExists(CrmClueSaveReqVO reqVO) { - // 校验负责人 - if (Objects.nonNull(reqVO.getOwnerUserId()) && - Objects.isNull(adminUserApi.getUser(reqVO.getOwnerUserId()))) { - throw exception(USER_NOT_EXISTS); - } + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("clueName", clue.getName()); } private CrmClueDO validateClueExists(Long id) { @@ -249,7 +215,7 @@ public class CrmClueServiceImpl implements CrmClueService { } @Override - @CrmPermission(bizType = CrmBizTypeEnum.CRM_LEADS, bizId = "#id", level = CrmPermissionLevelEnum.READ) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CLUE, bizId = "#id", level = CrmPermissionLevelEnum.READ) public CrmClueDO getClue(Long id) { return clueMapper.selectById(id); } @@ -267,13 +233,9 @@ public class CrmClueServiceImpl implements CrmClueService { return clueMapper.selectPage(pageReqVO, userId); } - /** - * 获得自身的代理对象,解决 AOP 生效问题 - * - * @return 自己 - */ - private CrmClueServiceImpl getSelf() { - return SpringUtil.getBean(getClass()); + @Override + public Long getFollowClueCount(Long userId) { + return clueMapper.selectCountByFollow(userId); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java index 8a9c4192f..2deaf0009 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessService.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.crm.service.contact; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusiness2ReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; import jakarta.validation.Valid; @@ -14,19 +15,33 @@ import java.util.List; public interface CrmContactBusinessService { /** - * 创建联系人与商机的关联 + * 创建联系人与商机的关联【通过联系人,关联商机】 * * @param createReqVO 创建信息 */ void createContactBusinessList(@Valid CrmContactBusinessReqVO createReqVO); /** - * 删除联系人与商机的关联 + * 创建联系人与商机的关联【通过商机,关联联系人】 + * + * @param createReqVO 创建信息 + */ + void createContactBusinessList2(@Valid CrmContactBusiness2ReqVO createReqVO); + + /** + * 删除联系人与商机的关联【通过联系人,取关商机】 * * @param deleteReqVO 删除信息 */ void deleteContactBusinessList(@Valid CrmContactBusinessReqVO deleteReqVO); + /** + * 删除联系人与商机的关联【通过商机,取关联系人】 + * + * @param deleteReqVO 删除信息 + */ + void deleteContactBusinessList2(@Valid CrmContactBusiness2ReqVO deleteReqVO); + /** * 删除联系人与商机的关联,基于联系人编号 * @@ -43,10 +58,11 @@ public interface CrmContactBusinessService { List getContactBusinessListByContactId(Long contactId); /** - * 新增联系人与商机的关联 + * 获得联系人与商机的关联列表,基于商机编号 * - * @param contactBusiness 新增联系人与商机的对象 + * @param businessId 商机编号 + * @return 联系人商机关联 */ - void insert(CrmContactBusinessDO contactBusiness); + List getContactBusinessListByBusinessId(Long businessId); } \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java index 7b0711334..c8850e3eb 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java @@ -1,11 +1,12 @@ package cn.iocoder.yudao.module.crm.service.contact; import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusiness2ReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusinessReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; -import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper; +import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactBusinessMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; @@ -67,6 +68,32 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService } } + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#createReqVO.businessId", level = CrmPermissionLevelEnum.WRITE) + public void createContactBusinessList2(CrmContactBusiness2ReqVO createReqVO) { + CrmBusinessDO business = businessService.getBusiness(createReqVO.getBusinessId()); + if (business == null) { + throw exception(BUSINESS_NOT_EXISTS); + } + // 遍历处理,考虑到一般数量不会太多,代码处理简单 + List saveDOList = new ArrayList<>(); + createReqVO.getContactIds().forEach(contactId -> { + CrmContactDO contact = contactService.getContact(contactId); + if (contact == null) { + throw exception(CONTACT_NOT_EXISTS); + } + // 关联判重 + if (contactBusinessMapper.selectByContactIdAndBusinessId(contactId, createReqVO.getBusinessId()) != null) { + return; + } + saveDOList.add(new CrmContactBusinessDO(null, contactId, createReqVO.getBusinessId())); + }); + // 批量插入 + if (CollUtil.isNotEmpty(saveDOList)) { + contactBusinessMapper.insertBatch(saveDOList); + } + } + @Override @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#deleteReqVO.contactId", level = CrmPermissionLevelEnum.WRITE) public void deleteContactBusinessList(CrmContactBusinessReqVO deleteReqVO) { @@ -79,6 +106,18 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService deleteReqVO.getContactId(), deleteReqVO.getBusinessIds()); } + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#deleteReqVO.businessId", level = CrmPermissionLevelEnum.WRITE) + public void deleteContactBusinessList2(CrmContactBusiness2ReqVO deleteReqVO) { + CrmBusinessDO business = businessService.getBusiness(deleteReqVO.getBusinessId()); + if (business == null) { + throw exception(BUSINESS_NOT_EXISTS); + } + // 直接删除 + contactBusinessMapper.deleteByBusinessIdAndContactId( + deleteReqVO.getBusinessId(), deleteReqVO.getContactIds()); + } + @Override @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.WRITE) public void deleteContactBusinessByContactId(Long contactId) { @@ -92,8 +131,9 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService } @Override - public void insert(CrmContactBusinessDO contactBusiness) { - contactBusinessMapper.insert(contactBusiness); + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#businessId", level = CrmPermissionLevelEnum.READ) + public List getContactBusinessListByBusinessId(Long businessId) { + return contactBusinessMapper.selectListByBusinessId(businessId); } } \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java index d7688b8fb..23c29d3bc 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactService.java @@ -4,13 +4,17 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; import jakarta.validation.Valid; +import java.time.LocalDateTime; import java.util.Collection; import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * CRM 联系人 Service 接口 @@ -51,7 +55,8 @@ public interface CrmContactService { void transferContact(CrmContactTransferReqVO reqVO, Long userId); /** - * 更新客户联系人负责人 + * 更新指定客户的联系人的负责人 + * 数据权限基于 【客户】 * * @param customerId 客户编号 * @param ownerUserId 用户编号 @@ -61,9 +66,19 @@ public interface CrmContactService { /** * 更新联系人相关跟进信息 * - * @param updateFollowUpReqBOList 跟进信息 + * @param id 编号 + * @param contactNextTime 下次联系时间 + * @param contactLastContent 最后联系内容 */ - void updateContactFollowUpBatch(List updateFollowUpReqBOList); + void updateContactFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent); + + /** + * 更新联系人的下次联系时间 + * + * @param ids 编号数组 + * @param contactNextTime 下次联系时间 + */ + void updateContactContactNextTime(Collection ids, LocalDateTime contactNextTime); /** * 获得联系人 @@ -74,13 +89,11 @@ public interface CrmContactService { CrmContactDO getContact(Long id); /** - * 获得联系人列表 + * 校验联系人 * - * @param ids 编号 - * @param userId 用户编号 - * @return 联系人列表 + * @param id 编号 */ - List getContactListByIds(Collection ids, Long userId); + void validateContact(Long id); /** * 获得联系人列表 @@ -88,14 +101,17 @@ public interface CrmContactService { * @param ids 编号 * @return 联系人列表 */ - List getContactListByIds(Collection ids); + List getContactList(Collection ids); /** - * 获得联系人列表 + * 获得联系人 Map * - * @return 联系人列表 + * @param ids 编号 + * @return 联系人 Map */ - List getContactList(); + default Map getContactMap(Collection ids) { + return convertMap(getContactList(ids), CrmContactDO::getId); + } /** * 获取联系人列表(校验权限) @@ -103,7 +119,7 @@ public interface CrmContactService { * @param userId 用户编号 * @return 联系人列表 */ - List getSimpleContactList(Long userId); + List getContactList(Long userId); /** * 获得联系人分页 @@ -126,6 +142,16 @@ public interface CrmContactService { */ PageResult getContactPageByCustomerId(CrmContactPageReqVO pageVO); + /** + * 获得联系人分页 + * + * 数据权限:基于 {@link CrmBusinessDO} + * + * @param pageVO 分页查询 + * @return 联系人分页 + */ + PageResult getContactPageByBusinessId(CrmContactPageReqVO pageVO); + /** * 获取关联客户的联系人数量 * diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java index 08ce78b81..d8e356124 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactServiceImpl.java @@ -8,7 +8,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactBusines import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactSaveReqVO; import cn.iocoder.yudao.module.crm.controller.admin.contact.vo.CrmContactTransferReqVO; -import cn.iocoder.yudao.module.crm.convert.contact.CrmContactConvert; +import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.mysql.contact.CrmContactMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; @@ -17,9 +17,9 @@ import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPerm import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; @@ -30,14 +30,16 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.time.LocalDateTime; import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS; import static java.util.Collections.singletonList; /** @@ -73,7 +75,7 @@ public class CrmContactServiceImpl implements CrmContactService { success = CRM_CONTACT_CREATE_SUCCESS) public Long createContact(CrmContactSaveReqVO createReqVO, Long userId) { createReqVO.setId(null); - // 1. 校验 + // 1. 校验关联数据 validateRelationDataExists(createReqVO); // 2. 插入联系人 @@ -102,8 +104,9 @@ public class CrmContactServiceImpl implements CrmContactService { success = CRM_CONTACT_UPDATE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) public void updateContact(CrmContactSaveReqVO updateReqVO) { - // 1. 校验存在 + // 1.1 校验存在 CrmContactDO oldContact = validateContactExists(updateReqVO.getId()); + // 1.2 校验关联数据 validateRelationDataExists(updateReqVO); // 2. 更新联系人 @@ -123,15 +126,15 @@ public class CrmContactServiceImpl implements CrmContactService { private void validateRelationDataExists(CrmContactSaveReqVO saveReqVO) { // 1. 校验客户 if (saveReqVO.getCustomerId() != null && customerService.getCustomer(saveReqVO.getCustomerId()) == null) { - throw exception(CUSTOMER_NOT_EXISTS); + customerService.validateCustomer(saveReqVO.getCustomerId()); } // 2. 校验负责人 - if (saveReqVO.getOwnerUserId() != null && adminUserApi.getUser(saveReqVO.getOwnerUserId()) == null) { - throw exception(USER_NOT_EXISTS); + if (saveReqVO.getOwnerUserId() != null) { + adminUserApi.validateUser(saveReqVO.getOwnerUserId()); } // 3. 直属上级 - if (saveReqVO.getParentId() != null && contactMapper.selectById(saveReqVO.getParentId()) == null) { - throw exception(CONTACT_NOT_EXISTS); + if (saveReqVO.getParentId() != null) { + validateContactExists(saveReqVO.getParentId()); } // 4. 如果有关联商机,则需要校验存在 if (saveReqVO.getBusinessId() != null && businessService.getBusiness(saveReqVO.getBusinessId()) == null) { @@ -165,11 +168,11 @@ public class CrmContactServiceImpl implements CrmContactService { } private CrmContactDO validateContactExists(Long id) { - CrmContactDO contactDO = contactMapper.selectById(id); - if (contactDO == null) { + CrmContactDO contact = contactMapper.selectById(id); + if (contact == null) { throw exception(CONTACT_NOT_EXISTS); } - return contactDO; + return contact; } @Override @@ -182,23 +185,63 @@ public class CrmContactServiceImpl implements CrmContactService { CrmContactDO contact = validateContactExists(reqVO.getId()); // 2.1 数据权限转移 - permissionService.transferPermission( - CrmContactConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTACT.getType())); + permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CONTACT.getType(), + reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel())); // 2.2 设置新的负责人 - contactMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + contactMapper.updateById(new CrmContactDO().setId(reqVO.getId()).setOwnerUserId(reqVO.getNewOwnerUserId())); // 3. 记录转移日志 LogRecordContext.putVariable("contact", contact); } @Override + @Transactional(rollbackFor = Exception.class) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#customerId", level = CrmPermissionLevelEnum.OWNER) public void updateOwnerUserIdByCustomerId(Long customerId, Long ownerUserId) { - contactMapper.updateOwnerUserIdByCustomerId(customerId, ownerUserId); + // 1. 校验存在 + List contacts = contactMapper.selectListByCustomerId(customerId); + if (CollUtil.isEmpty(contacts)) { + return; + } + int count = contactMapper.updateOwnerUserIdByCustomerId(customerId, ownerUserId); + if (count == 0) { + throw exception(CONTACT_UPDATE_OWNER_USER_FAIL); + } + + // 2. 记录操作日志 + for (CrmContactDO contact : contacts) { + receiveContactLog(contact, ownerUserId); + } + } + + @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_UPDATE_OWNER_USER_SUB_TYPE, bizNo = "{{#contact.id}", + success = CRM_CONTACT_UPDATE_OWNER_USER_SUCCESS) + public void receiveContactLog(CrmContactDO contact, Long ownerUserId) { + // 记录操作日志上下文 + LogRecordContext.putVariable("contact", contact); + LogRecordContext.putVariable("ownerUserId", ownerUserId); } @Override - public void updateContactFollowUpBatch(List updateFollowUpReqBOList) { - contactMapper.updateBatch(CrmContactConvert.INSTANCE.convertList(updateFollowUpReqBOList)); + @LogRecord(type = CRM_CONTACT_TYPE, subType = CRM_CONTACT_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}", + success = CRM_CONTACT_FOLLOW_UP_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) + public void updateContactFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) { + // 1. 校验存在 + CrmContactDO contact = validateContactExists(id); + + // 2. 更新联系人的跟进信息 + contactMapper.updateById(new CrmContactDO().setId(id).setContactNextTime(contactNextTime) + .setContactLastTime(LocalDateTime.now()).setContactLastContent(contactLastContent)); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("contactName", contact.getName()); + } + + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#ids", level = CrmPermissionLevelEnum.WRITE) + public void updateContactContactNextTime(Collection ids, LocalDateTime contactNextTime) { + contactMapper.updateBatch(convertList(ids, id -> new CrmContactDO().setId(id).setContactNextTime(contactNextTime))); } //======================= 查询相关 ======================= @@ -210,15 +253,12 @@ public class CrmContactServiceImpl implements CrmContactService { } @Override - public List getContactListByIds(Collection ids, Long userId) { - if (CollUtil.isEmpty(ids)) { - return ListUtil.empty(); - } - return contactMapper.selectBatchIds(ids, userId); + public void validateContact(Long id) { + validateContactExists(id); } @Override - public List getContactListByIds(Collection ids) { + public List getContactList(Collection ids) { if (CollUtil.isEmpty(ids)) { return ListUtil.empty(); } @@ -226,12 +266,7 @@ public class CrmContactServiceImpl implements CrmContactService { } @Override - public List getContactList() { - return contactMapper.selectList(); - } - - @Override - public List getSimpleContactList(Long userId) { + public List getContactList(Long userId) { CrmContactPageReqVO reqVO = new CrmContactPageReqVO(); reqVO.setPageSize(PAGE_SIZE_NONE); // 不分页 return contactMapper.selectPage(reqVO, userId).getList(); @@ -248,6 +283,16 @@ public class CrmContactServiceImpl implements CrmContactService { return contactMapper.selectPageByCustomerId(pageVO); } + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#pageVO.businessId", level = CrmPermissionLevelEnum.READ) + public PageResult getContactPageByBusinessId(CrmContactPageReqVO pageVO) { + List contactBusinessList = contactBusinessService.getContactBusinessListByBusinessId(pageVO.getBusinessId()); + if (CollUtil.isEmpty(contactBusinessList)) { + return PageResult.empty(); + } + return contactMapper.selectPageByBusinessId(pageVO, convertSet(contactBusinessList, CrmContactBusinessDO::getContactId)); + } + @Override public Long getContactCountByCustomerId(Long customerId) { return contactMapper.selectCount(CrmContactDO::getCustomerId, customerId); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractConfigService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractConfigService.java new file mode 100644 index 000000000..79471aaf9 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractConfigService.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.crm.service.contract; + +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config.CrmContractConfigSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO; +import jakarta.validation.Valid; + +/** + * 合同配置 Service 接口 + * + * @author 芋道源码 + */ +public interface CrmContractConfigService { + + /** + * 获得合同配置 + * + * @return 合同配置 + */ + CrmContractConfigDO getContractConfig(); + + /** + * 保存合同配置 + * + * @param saveReqVO 更新信息 + */ + void saveContractConfig(@Valid CrmContractConfigSaveReqVO saveReqVO); + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractConfigServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractConfigServiceImpl.java new file mode 100644 index 000000000..c9379dcec --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractConfigServiceImpl.java @@ -0,0 +1,56 @@ +package cn.iocoder.yudao.module.crm.service.contract; + +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.config.CrmContractConfigSaveReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO; +import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractConfigMapper; +import com.mzt.logapi.context.LogRecordContext; +import com.mzt.logapi.starter.annotation.LogRecord; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import java.util.Objects; + +import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; + +/** + * 合同配置 Service 实现类 + * + * @author 芋道源码 + */ +@Service +@Validated +public class CrmContractConfigServiceImpl implements CrmContractConfigService { + + @Resource + private CrmContractConfigMapper contractConfigMapper; + + @Override + public CrmContractConfigDO getContractConfig() { + return contractConfigMapper.selectOne(); + } + + @Override + @LogRecord(type = CRM_CONTRACT_CONFIG_TYPE, subType = CRM_CONTRACT_CONFIG_SUB_TYPE, bizNo = "{{#configId}}", + success = CRM_CONTRACT_CONFIG_SUCCESS) + public void saveContractConfig(CrmContractConfigSaveReqVO saveReqVO) { + // 1. 存在,则进行更新 + CrmContractConfigDO dbConfig = getContractConfig(); + CrmContractConfigDO config = BeanUtils.toBean(saveReqVO, CrmContractConfigDO.class); + if (Objects.nonNull(dbConfig)) { + contractConfigMapper.updateById(config.setId(dbConfig.getId())); + // 记录操作日志上下文 + LogRecordContext.putVariable("isConfigUpdate", Boolean.TRUE); + LogRecordContext.putVariable("configId", config.getId()); + return; + } + + // 2. 不存在,则进行插入 + contractConfigMapper.insert(config); + // 记录操作日志上下文 + LogRecordContext.putVariable("isConfigUpdate", Boolean.FALSE); + LogRecordContext.putVariable("configId", config.getId()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java index 0bd527ae0..25f5537dc 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java @@ -1,18 +1,21 @@ package cn.iocoder.yudao.module.crm.service.contract; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; import jakarta.validation.Valid; +import java.time.LocalDateTime; import java.util.Collection; import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * CRM 合同 Service 接口 @@ -55,9 +58,11 @@ public interface CrmContractService { /** * 更新合同相关的更进信息 * - * @param contractUpdateFollowUpReqBO 信息 + * @param id 合同编号 + * @param contactNextTime 下次联系时间 + * @param contactLastContent 最后联系内容 */ - void updateContractFollowUp(CrmUpdateFollowUpReqBO contractUpdateFollowUpReqBO); + void updateContractFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent); /** * 发起合同审批流程 @@ -70,9 +75,10 @@ public interface CrmContractService { /** * 更新合同流程审批结果 * - * @param event 审批结果 + * @param id 合同编号 + * @param bpmResult BPM 审批结果 */ - void updateContractAuditStatus(BpmResultListenerRespDTO event); + void updateContractAuditStatus(Long id, Integer bpmResult); /** * 获得合同 @@ -82,6 +88,14 @@ public interface CrmContractService { */ CrmContractDO getContract(Long id); + /** + * 校验合同是否合法 + * + * @param id 编号 + * @return 合同 + */ + CrmContractDO validateContract(Long id); + /** * 获得合同列表 * @@ -90,6 +104,16 @@ public interface CrmContractService { */ List getContractList(Collection ids); + /** + * 获得合同 Map + * + * @param ids 编号 + * @return 合同 Map + */ + default Map getContractMap(Collection ids) { + return convertMap(getContractList(ids), CrmContractDO::getId); + } + /** * 获得合同分页 * @@ -107,10 +131,20 @@ public interface CrmContractService { * 数据权限:基于 {@link CrmCustomerDO} 读取 * * @param pageReqVO 分页查询 - * @return 联系人分页 + * @return 合同分页 */ PageResult getContractPageByCustomerId(CrmContractPageReqVO pageReqVO); + /** + * 获得合同分页,基于指定商机 + * + * 数据权限:基于 {@link CrmBusinessDO} 读取 + * + * @param pageReqVO 分页查询 + * @return 合同分页 + */ + PageResult getContractPageByBusinessId(CrmContractPageReqVO pageReqVO); + /** * 查询属于某个联系人的合同数量 * @@ -128,7 +162,7 @@ public interface CrmContractService { Long getContractCountByCustomerId(Long customerId); /** - * 根据商机ID获取关联客户的合同数量 + * 根据商机编号,获取关联客户的合同数量 * * @param businessId 商机编号 * @return 数量 @@ -136,11 +170,27 @@ public interface CrmContractService { Long getContractCountByBusinessId(Long businessId); /** - * 获取合同商品列表 + * 根据合同编号,获得合同的产品列表 * * @param contactId 合同编号 - * @return 合同商品列表 + * @return 产品列表 */ List getContractProductListByContractId(Long contactId); + /** + * 获得待审核合同数量 + * + * @param userId 用户编号 + * @return 提醒数量 + */ + Long getAuditContractCount(Long userId); + + /** + * 获得即将到期(提醒)的合同数量 + * + * @param userId 用户编号 + * @return 提醒数量 + */ + Long getRemindContractCount(Long userId); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java index 79d05263c..e8a8dbf59 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java @@ -8,49 +8,48 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.number.MoneyUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; -import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO; import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; -import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO; -import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractSaveReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.contract.CrmContractTransferReqVO; +import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractConfigDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractProductDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO; import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper; import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractProductMapper; +import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO; import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; -import cn.iocoder.yudao.module.crm.service.business.bo.CrmBusinessUpdateProductReqBO; +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; import cn.iocoder.yudao.module.crm.service.product.CrmProductService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.math.BigDecimal; +import java.time.LocalDateTime; import java.util.Collection; import java.util.List; -import java.util.Map; -import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; -import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS; +import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus; /** * CRM 合同 Service 实现类 @@ -59,18 +58,22 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_E */ @Service @Validated +@Slf4j public class CrmContractServiceImpl implements CrmContractService { /** * BPM 合同审批流程标识 */ - public static final String CONTRACT_APPROVE = "contract-approve"; + public static final String BPM_PROCESS_DEFINITION_KEY = "crm-contract-audit"; @Resource private CrmContractMapper contractMapper; @Resource private CrmContractProductMapper contractProductMapper; + @Resource + private CrmNoRedisDAO noRedisDAO; + @Resource private CrmPermissionService crmPermissionService; @Resource @@ -79,6 +82,10 @@ public class CrmContractServiceImpl implements CrmContractService { private CrmCustomerService customerService; @Resource private CrmBusinessService businessService; + @Resource + private CrmContactService contactService; + @Resource + private CrmContractConfigService contractConfigService; @Resource private AdminUserApi adminUserApi; @@ -90,30 +97,32 @@ public class CrmContractServiceImpl implements CrmContractService { @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_CREATE_SUB_TYPE, bizNo = "{{#contract.id}}", success = CRM_CONTRACT_CREATE_SUCCESS) public Long createContract(CrmContractSaveReqVO createReqVO, Long userId) { + // 1.1 校验产品项的有效性 + List contractProducts = validateContractProducts(createReqVO.getProducts()); + // 1.2 校验关联字段 validateRelationDataExists(createReqVO); - // 1.1 插入合同 - CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setId(null); - contractMapper.insert(contract); - // 1.2 插入合同关联商品 - if (CollUtil.isNotEmpty(createReqVO.getProductItems())) { // 如果有的话 - List productList = convertContractProductList(createReqVO, contract.getId()); - contractProductMapper.insertBatch(productList); - // 更新合同商品总金额 - contractMapper.updateById(new CrmContractDO().setId(contract.getId()).setProductPrice( - getSumValue(productList, CrmContractProductDO::getTotalPrice, Integer::sum))); - // 如果存在合同关联了商机则更新商机商品关联 - if (contract.getBusinessId() != null) { - businessService.updateBusinessProduct(new CrmBusinessUpdateProductReqBO().setId(contract.getBusinessId()) - .setProductItems(BeanUtils.toBean(createReqVO.getProductItems(), CrmBusinessUpdateProductReqBO.CrmBusinessProductItem.class))); - } + // 1.3 生成序号 + String no = noRedisDAO.generate(CrmNoRedisDAO.CONTRACT_NO_PREFIX); + if (contractMapper.selectByNo(no) != null) { + throw exception(CONTRACT_NO_EXISTS); } - // 2. 创建数据权限 - crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) + // 2.1 插入合同 + CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setNo(no); + calculateTotalPrice(contract, contractProducts); + contractMapper.insert(contract); + // 2.2 插入合同关联商品 + if (CollUtil.isNotEmpty(contractProducts)) { + contractProducts.forEach(item -> item.setContractId(contract.getId())); + contractProductMapper.insertBatch(contractProducts); + } + + // 3. 创建数据权限 + crmPermissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(contract.getOwnerUserId()) .setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()).setBizId(contract.getId()) .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); - // 3. 记录操作日志上下文 + // 4. 记录操作日志上下文 LogRecordContext.putVariable("contract", contract); return contract.getId(); } @@ -125,69 +134,47 @@ public class CrmContractServiceImpl implements CrmContractService { @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) public void updateContract(CrmContractSaveReqVO updateReqVO) { Assert.notNull(updateReqVO.getId(), "合同编号不能为空"); + updateReqVO.setOwnerUserId(null); // 不允许更新的字段 // 1.1 校验存在 CrmContractDO contract = validateContractExists(updateReqVO.getId()); // 1.2 只有草稿、审批中,可以编辑; if (!ObjectUtils.equalsAny(contract.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { - throw exception(CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED); + throw exception(CONTRACT_UPDATE_FAIL_NOT_DRAFT); } + // 1.3 校验产品项的有效性 + List contractProducts = validateContractProducts(updateReqVO.getProducts()); + // 1.4 校验关联字段 validateRelationDataExists(updateReqVO); // 2.1 更新合同 CrmContractDO updateObj = BeanUtils.toBean(updateReqVO, CrmContractDO.class); + calculateTotalPrice(updateObj, contractProducts); contractMapper.updateById(updateObj); // 2.2 更新合同关联商品 - updateContractProduct(updateReqVO, updateObj.getId()); + updateContractProduct(updateReqVO.getId(), contractProducts); // 3. 记录操作日志上下文 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(contract, CrmContractSaveReqVO.class)); LogRecordContext.putVariable("contractName", contract.getName()); } - private void updateContractProduct(CrmContractSaveReqVO updateReqVO, Long contractId) { - if (CollUtil.isEmpty(updateReqVO.getProductItems())) { - return; - } - List newProductList = convertContractProductList(updateReqVO, contractId); - List oldProductList = contractProductMapper.selectListByContractId(contractId); - List> diffList = diffList(oldProductList, newProductList, (oldObj, newObj) -> { - boolean match = ObjUtil.equal(oldObj.getProductId(), newObj.getProductId()); - if (match) { - newObj.setId(oldObj.getId()); // 设置一下老的编号更新时需要使用 - } - return match; - }); + private void updateContractProduct(Long id, List newList) { + List oldList = contractProductMapper.selectListByContractId(id); + List> diffList = diffList(oldList, newList, // id 不同,就认为是不同的记录 + (oldVal, newVal) -> oldVal.getId().equals(newVal.getId())); if (CollUtil.isNotEmpty(diffList.get(0))) { + diffList.get(0).forEach(o -> o.setContractId(id)); contractProductMapper.insertBatch(diffList.get(0)); } if (CollUtil.isNotEmpty(diffList.get(1))) { contractProductMapper.updateBatch(diffList.get(1)); } if (CollUtil.isNotEmpty(diffList.get(2))) { - contractProductMapper.deleteBatchIds(convertList(diffList.get(2), CrmContractProductDO::getId)); + contractProductMapper.deleteBatchIds(convertSet(diffList.get(2), CrmContractProductDO::getId)); } } - // TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum - - private List convertContractProductList(CrmContractSaveReqVO reqVO, Long contractId) { - // 校验商品存在 - Set productIds = convertSet(reqVO.getProductItems(), CrmContractSaveReqVO.CrmContractProductItem::getId); - List productList = productService.getProductList(productIds); - if (CollUtil.isEmpty(productIds) || productList.size() != productIds.size()) { - throw exception(PRODUCT_NOT_EXISTS); - } - Map productMap = convertMap(productList, CrmProductDO::getId); - return convertList(reqVO.getProductItems(), productItem -> { - CrmProductDO product = productMap.get(productItem.getId()); - return BeanUtils.toBean(product, CrmContractProductDO.class) - .setId(null).setProductId(productItem.getId()).setContractId(contractId) - .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent()) - .setTotalPrice(MoneyUtils.calculator(product.getPrice(), productItem.getCount(), productItem.getDiscountPercent())); - }); - } - /** * 校验关联数据是否存在 * @@ -195,17 +182,38 @@ public class CrmContractServiceImpl implements CrmContractService { */ private void validateRelationDataExists(CrmContractSaveReqVO reqVO) { // 1. 校验客户 - if (reqVO.getCustomerId() != null && customerService.getCustomer(reqVO.getCustomerId()) == null) { - throw exception(CUSTOMER_NOT_EXISTS); + if (reqVO.getCustomerId() != null) { + customerService.validateCustomer(reqVO.getCustomerId()); } // 2. 校验负责人 - if (reqVO.getOwnerUserId() != null && adminUserApi.getUser(reqVO.getOwnerUserId()) == null) { - throw exception(USER_NOT_EXISTS); + if (reqVO.getOwnerUserId() != null) { + adminUserApi.validateUser(reqVO.getOwnerUserId()); } // 3. 如果有关联商机,则需要校验存在 - if (reqVO.getBusinessId() != null && businessService.getBusiness(reqVO.getBusinessId()) == null) { - throw exception(BUSINESS_NOT_EXISTS); + if (reqVO.getBusinessId() != null) { + businessService.validateBusiness(reqVO.getBusinessId()); } + // 4. 校验签约相关字段 + if (reqVO.getSignContactId() != null) { + contactService.validateContact(reqVO.getSignContactId()); + } + if (reqVO.getSignUserId() != null) { + adminUserApi.validateUser(reqVO.getSignUserId()); + } + } + + private List validateContractProducts(List list) { + // 1. 校验产品存在 + productService.validProductList(convertSet(list, CrmContractSaveReqVO.Product::getProductId)); + // 2. 转化为 CrmContractProductDO 列表 + return convertList(list, o -> BeanUtils.toBean(o, CrmContractProductDO.class, + item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getContractPrice(), item.getCount())))); + } + + private void calculateTotalPrice(CrmContractDO contract, List contractProducts) { + contract.setTotalProductPrice(getSumValue(contractProducts, CrmContractProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO)); + BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(contract.getTotalProductPrice(), contract.getDiscountPercent()); + contract.setTotalPrice(contract.getTotalProductPrice().subtract(discountPrice)); } @Override @@ -214,7 +222,7 @@ public class CrmContractServiceImpl implements CrmContractService { success = CRM_CONTRACT_DELETE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteContract(Long id) { - // TODO @合同待定:如果被 CrmReceivableDO 所使用,则不允许删除 + // TODO @puhui999:如果被 CrmReceivableDO 所使用,则不允许删除 // 校验存在 CrmContractDO contract = validateContractExists(id); // 删除 @@ -244,18 +252,28 @@ public class CrmContractServiceImpl implements CrmContractService { CrmContractDO contract = validateContractExists(reqVO.getId()); // 2.1 数据权限转移 - crmPermissionService.transferPermission( - CrmContractConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType())); + crmPermissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CONTRACT.getType(), + reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel())); // 2.2 设置负责人 - contractMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + contractMapper.updateById(new CrmContractDO().setId(reqVO.getId()).setOwnerUserId(reqVO.getNewOwnerUserId())); // 3. 记录转移日志 LogRecordContext.putVariable("contract", contract); } @Override - public void updateContractFollowUp(CrmUpdateFollowUpReqBO contractUpdateFollowUpReqBO) { - contractMapper.updateById(BeanUtils.toBean(contractUpdateFollowUpReqBO, CrmContractDO.class).setId(contractUpdateFollowUpReqBO.getBizId())); + @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}", + success = CRM_CONTRACT_FOLLOW_UP_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) + public void updateContractFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) { + // 1. 校验存在 + CrmContractDO contract = validateContractExists(id); + + // 2. 更新联系人的跟进信息 + contractMapper.updateById(new CrmContractDO().setId(id).setContactLastTime(LocalDateTime.now())); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("contractName", contract.getName()); } @Override @@ -271,7 +289,7 @@ public class CrmContractServiceImpl implements CrmContractService { // 2. 创建合同审批流程实例 String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() - .setProcessDefinitionKey(CONTRACT_APPROVE).setBusinessKey(String.valueOf(id))); + .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY).setBusinessKey(String.valueOf(id))); // 3. 更新合同工作流编号 contractMapper.updateById(new CrmContractDO().setId(id).setProcessInstanceId(processInstanceId) @@ -282,38 +300,22 @@ public class CrmContractServiceImpl implements CrmContractService { } @Override - public void updateContractAuditStatus(BpmResultListenerRespDTO event) { - // 判断下状态是否符合预期 - if (!isEndResult(event.getResult())) { - return; + public void updateContractAuditStatus(Long id, Integer bpmResult) { + // 1.1 校验合同是否存在 + CrmContractDO contract = validateContractExists(id); + // 1.2 只有审批中,可以更新审批结果 + if (ObjUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { + log.error("[updateContractAuditStatus][contract({}) 不处于审批中,无法更新审批结果({})]", + contract.getId(), bpmResult); + throw exception(CONTRACT_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS); } - // 状态转换 - if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.APPROVE.getResult())) { - event.setResult(CrmAuditStatusEnum.APPROVE.getStatus()); - } - if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.REJECT.getResult())) { - event.setResult(CrmAuditStatusEnum.REJECT.getStatus()); - } - if (ObjUtil.equal(event.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult())) { - event.setResult(CrmAuditStatusEnum.CANCEL.getStatus()); - } - // 更新合同状态 - contractMapper.updateById(new CrmContractDO().setId(Long.parseLong(event.getBusinessKey())) - .setAuditStatus(event.getResult())); + + // 2. 更新合同审批结果 + Integer auditStatus = convertBpmResultToAuditStatus(bpmResult); + contractMapper.updateById(new CrmContractDO().setId(id).setAuditStatus(auditStatus)); } - /** - * 判断该结果是否处于 End 最终结果 - * - * @param result 结果 - * @return 是否 - */ - public static boolean isEndResult(Integer result) { - return ObjectUtils.equalsAny(result, BpmProcessInstanceResultEnum.APPROVE.getResult(), - BpmProcessInstanceResultEnum.REJECT.getResult(), BpmProcessInstanceResultEnum.CANCEL.getResult()); - } - - //======================= 查询相关 ======================= + // ======================= 查询相关 ======================= @Override @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#id", level = CrmPermissionLevelEnum.READ) @@ -321,6 +323,11 @@ public class CrmContractServiceImpl implements CrmContractService { return contractMapper.selectById(id); } + @Override + public CrmContractDO validateContract(Long id) { + return validateContractExists(id); + } + @Override public List getContractList(Collection ids) { if (CollUtil.isEmpty(ids)) { @@ -331,7 +338,16 @@ public class CrmContractServiceImpl implements CrmContractService { @Override public PageResult getContractPage(CrmContractPageReqVO pageReqVO, Long userId) { - return contractMapper.selectPage(pageReqVO, userId); + // 1. 即将到期,需要查询合同配置 + CrmContractConfigDO config = null; + if (CrmContractPageReqVO.EXPIRY_TYPE_ABOUT_TO_EXPIRE.equals(pageReqVO.getExpiryType())) { + config = contractConfigService.getContractConfig(); + if (config != null && Boolean.FALSE.equals(config.getNotifyEnabled())) { + config = null; + } + } + // 2. 查询分页 + return contractMapper.selectPage(pageReqVO, userId, config); } @Override @@ -340,6 +356,12 @@ public class CrmContractServiceImpl implements CrmContractService { return contractMapper.selectPageByCustomerId(pageReqVO); } + @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_BUSINESS, bizId = "#pageReqVO.businessId", level = CrmPermissionLevelEnum.READ) + public PageResult getContractPageByBusinessId(CrmContractPageReqVO pageReqVO) { + return contractMapper.selectPageByBusinessId(pageReqVO); + } + @Override public Long getContractCountByContactId(Long contactId) { return contractMapper.selectCountByContactId(contactId); @@ -360,5 +382,18 @@ public class CrmContractServiceImpl implements CrmContractService { return contractProductMapper.selectListByContractId(contactId); } - // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒; + @Override + public Long getAuditContractCount(Long userId) { + return contractMapper.selectCountByAudit(userId); + } + + @Override + public Long getRemindContractCount(Long userId) { + CrmContractConfigDO config = contractConfigService.getContractConfig(); + if (config == null || Boolean.FALSE.equals(config.getNotifyEnabled())) { + return 0L; + } + return contractMapper.selectCountByRemind(userId, config); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java index c5ee407bd..256259bdf 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java @@ -1,32 +1,31 @@ package cn.iocoder.yudao.module.crm.service.contract.listener; -import cn.iocoder.yudao.module.bpm.api.listener.BpmResultListenerApi; -import cn.iocoder.yudao.module.bpm.api.listener.dto.BpmResultListenerRespDTO; +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent; +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEventListener; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.crm.service.contract.CrmContractServiceImpl; import jakarta.annotation.Resource; import org.springframework.stereotype.Component; -// TODO @芋艿:后续改成支持 RPC /** * 合同审批的结果的监听器实现类 * * @author HUIHUI */ @Component -public class CrmContractResultListener implements BpmResultListenerApi { +public class CrmContractResultListener extends BpmProcessInstanceResultEventListener { @Resource private CrmContractService contractService; @Override public String getProcessDefinitionKey() { - return CrmContractServiceImpl.CONTRACT_APPROVE; + return CrmContractServiceImpl.BPM_PROCESS_DEFINITION_KEY; } @Override - public void onEvent(BpmResultListenerRespDTO event) { - contractService.updateContractAuditStatus(event); + protected void onEvent(BpmProcessInstanceResultEvent event) { + contractService.updateContractAuditStatus(Long.parseLong(event.getBusinessKey()), event.getResult()); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java index 303a758d2..9099b1954 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerPoolConfigServiceImpl.java @@ -26,26 +26,16 @@ public class CrmCustomerPoolConfigServiceImpl implements CrmCustomerPoolConfigSe @Resource private CrmCustomerPoolConfigMapper customerPoolConfigMapper; - /** - * 获得客户公海配置 - * - * @return 客户公海配置 - */ @Override public CrmCustomerPoolConfigDO getCustomerPoolConfig() { return customerPoolConfigMapper.selectOne(); } - /** - * 保存客户公海配置 - * - * @param saveReqVO 更新信息 - */ @Override @LogRecord(type = CRM_CUSTOMER_POOL_CONFIG_TYPE, subType = CRM_CUSTOMER_POOL_CONFIG_SUB_TYPE, bizNo = "{{#poolConfigId}}", success = CRM_CUSTOMER_POOL_CONFIG_SUCCESS) public void saveCustomerPoolConfig(CrmCustomerPoolConfigSaveReqVO saveReqVO) { - // 存在,则进行更新 + // 1. 存在,则进行更新 CrmCustomerPoolConfigDO dbConfig = getCustomerPoolConfig(); CrmCustomerPoolConfigDO poolConfig = BeanUtils.toBean(saveReqVO, CrmCustomerPoolConfigDO.class); if (Objects.nonNull(dbConfig)) { @@ -55,7 +45,8 @@ public class CrmCustomerPoolConfigServiceImpl implements CrmCustomerPoolConfigSe LogRecordContext.putVariable("poolConfigId", poolConfig.getId()); return; } - // 不存在,则进行插入 + + // 2. 不存在,则进行插入 customerPoolConfigMapper.insert(poolConfig); // 记录操作日志上下文 LogRecordContext.putVariable("isPoolConfigUpdate", Boolean.FALSE); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java index 911260f76..a99269e08 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java @@ -1,15 +1,17 @@ package cn.iocoder.yudao.module.crm.service.customer; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; import jakarta.validation.Valid; +import java.time.LocalDateTime; import java.util.Collection; import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * 客户 Service 接口 @@ -34,6 +36,23 @@ public interface CrmCustomerService { */ void updateCustomer(@Valid CrmCustomerSaveReqVO updateReqVO); + /** + * 更新客户的跟进状态 + * + * @param id 编号 + * @param dealStatus 跟进状态 + */ + void updateCustomerDealStatus(Long id, Boolean dealStatus); + + /** + * 更新客户相关的跟进信息 + * + * @param id 编号 + * @param contactNextTime 下次联系时间 + * @param contactLastContent 最后联系内容 + */ + void updateCustomerFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent); + /** * 删除客户 * @@ -58,6 +77,16 @@ public interface CrmCustomerService { */ List getCustomerList(Collection ids); + /** + * 获得客户 Map + * + * @param ids 客户编号数组 + * @return 客户 Map + */ + default Map getCustomerMap(Collection ids) { + return convertMap(getCustomerList(ids), CrmCustomerDO::getId); + } + /** * 获得客户分页 * @@ -67,12 +96,45 @@ public interface CrmCustomerService { */ PageResult getCustomerPage(CrmCustomerPageReqVO pageReqVO, Long userId); + /** + * 获得放入公海提醒的客户分页 + * + * @param pageVO 分页查询 + * @param userId 用户编号 + * @return 客户分页 + */ + PageResult getPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, Long userId); + + /** + * 获得待进入公海的客户数量 + * + * @param userId 用户编号 + * @return 提醒数量 + */ + Long getPutPoolRemindCustomerCount(Long userId); + + /** + * 获得今日需联系客户数量 + * + * @param userId 用户编号 + * @return 提醒数量 + */ + Long getTodayContactCustomerCount(Long userId); + + /** + * 获得分配给我的客户数量 + * + * @param userId 用户编号 + * @return 提醒数量 + */ + Long getFollowCustomerCount(Long userId); + /** * 校验客户是否存在 * - * @param customerId 客户 id + * @param id 编号 */ - void validateCustomer(Long customerId); + void validateCustomer(Long id); /** * 客户转移 @@ -90,13 +152,6 @@ public interface CrmCustomerService { */ void lockCustomer(@Valid CrmCustomerLockReqVO lockReqVO, Long userId); - /** - * 更新客户相关更进信息 - * - * @param customerUpdateFollowUpReqBO 请求 - */ - void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO); - /** * 创建客户 * @@ -129,7 +184,7 @@ public interface CrmCustomerService { * * @param ids 要领取的客户编号数组 * @param ownerUserId 负责人 - * @param isReceive 是/否领取 + * @param isReceive 是/否领取;true - 领取;false - 分配 */ void receiveCustomer(List ids, Long ownerUserId, Boolean isReceive); @@ -140,7 +195,4 @@ public interface CrmCustomerService { */ int autoPutCustomerPool(); - PageResult getPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, - CrmCustomerPoolConfigDO poolConfigDO, - Long loginUserId); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java index bc02a6b71..314349865 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java @@ -8,25 +8,23 @@ import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; -import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; -import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import com.mzt.logapi.context.LogRecordContext; @@ -114,7 +112,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { */ private static CrmCustomerDO initCustomer(Object customer, Long ownerUserId) { return BeanUtils.toBean(customer, CrmCustomerDO.class).setOwnerUserId(ownerUserId) - .setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now()); + .setOwnerTime(LocalDateTime.now()); } @Override @@ -137,6 +135,42 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { LogRecordContext.putVariable("customerName", oldCustomer.getName()); } + @Override + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) + public void updateCustomerDealStatus(Long id, Boolean dealStatus) { + // 1.1 校验存在 + CrmCustomerDO customer = validateCustomerExists(id); + // 1.2 校验是否重复操作 + if (Objects.equals(customer.getDealStatus(), dealStatus)) { + throw exception(CUSTOMER_UPDATE_DEAL_STATUS_FAIL); + } + + // 2. 更新客户的成交状态 + customerMapper.updateById(new CrmCustomerDO().setId(id).setDealStatus(dealStatus)); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("customerName", customer.getName()); + LogRecordContext.putVariable("dealStatus", dealStatus); + } + + @Override + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}", + success = CRM_CUSTOMER_FOLLOW_UP_SUCCESS) + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) + public void updateCustomerFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) { + // 1.1 校验存在 + CrmCustomerDO customer = validateCustomerExists(id); + + // 2. 更新客户的跟进信息 + customerMapper.updateById(new CrmCustomerDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime) + .setContactLastTime(LocalDateTime.now()).setContactLastContent(contactLastContent)); + + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("customerName", customer.getName()); + } + @Override @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_DELETE_SUB_TYPE, bizNo = "{{#id}}", @@ -146,9 +180,9 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { // 1.1 校验存在 CrmCustomerDO customer = validateCustomerExists(id); // 1.2 检查引用 - checkCustomerReference(id); + validateCustomerReference(id); - // 2. 删除 + // 2. 删除客户 customerMapper.deleteById(id); // 3. 删除数据权限 permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); @@ -157,23 +191,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { LogRecordContext.putVariable("customerName", customer.getName()); } - /** - * 校验客户是否被引用 - * - * @param id 客户编号 - */ - private void checkCustomerReference(Long id) { - if (contactService.getContactCountByCustomerId(id) > 0) { - throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_CONTACT.getName()); - } - if (businessService.getBusinessCountByCustomerId(id) > 0) { - throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_BUSINESS.getName()); - } - if (contractService.getContractCountByCustomerId(id) > 0) { - throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_CONTRACT.getName()); - } - } - @Override @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_TRANSFER_SUB_TYPE, bizNo = "{{#reqVO.id}}", @@ -186,10 +203,11 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { validateCustomerExceedOwnerLimit(reqVO.getNewOwnerUserId(), 1); // 2.1 数据权限转移 - permissionService.transferPermission( - CrmCustomerConvert.INSTANCE.convert(reqVO, userId).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())); + permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(), + reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel())); // 2.2 转移后重新设置负责人 - customerMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); + customerMapper.updateById(new CrmCustomerDO().setId(reqVO.getId()) + .setOwnerUserId(reqVO.getNewOwnerUserId()).setOwnerTime(LocalDateTime.now())); // 3. 记录转移日志 LogRecordContext.putVariable("customer", customer); @@ -206,7 +224,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { if (customer.getLockStatus().equals(lockReqVO.getLockStatus())) { throw exception(customer.getLockStatus() ? CUSTOMER_LOCK_FAIL_IS_LOCK : CUSTOMER_UNLOCK_FAIL_IS_UNLOCK); } - // 1.3 校验锁定上限。 + // 1.3 校验锁定上限 if (lockReqVO.getLockStatus()) { validateCustomerExceedLockLimit(userId); } @@ -219,19 +237,13 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { LogRecordContext.putVariable("customer", customer); } - @Override - public void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO) { - customerMapper.updateById(BeanUtils.toBean(customerUpdateFollowUpReqBO, CrmCustomerDO.class).setId(customerUpdateFollowUpReqBO.getBizId())); - } - @Override @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}", success = CRM_CUSTOMER_CREATE_SUCCESS) - public Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId) { + public Long createCustomer(CrmCustomerCreateReqBO createReqBO, Long userId) { // 1. 插入客户 - CrmCustomerDO customer = BeanUtils.toBean(customerCreateReq, CrmCustomerDO.class).setOwnerUserId(userId) - .setLockStatus(false).setDealStatus(false).setReceiveTime(LocalDateTime.now()); + CrmCustomerDO customer = initCustomer(createReqBO, userId); customerMapper.insert(customer); // 2. 创建数据权限 @@ -244,15 +256,19 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { } @Override - public CrmCustomerImportRespVO importCustomerList(List importCustomers, CrmCustomerImportReqVO importReqVO) { + public CrmCustomerImportRespVO importCustomerList(List importCustomers, + CrmCustomerImportReqVO importReqVO) { + // 校验非空 + importCustomers = filterList(importCustomers, item -> Objects.nonNull(item.getName())); if (CollUtil.isEmpty(importCustomers)) { throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY); } + + // 逐条处理 CrmCustomerImportRespVO respVO = CrmCustomerImportRespVO.builder().createCustomerNames(new ArrayList<>()) .updateCustomerNames(new ArrayList<>()).failureCustomerNames(new LinkedHashMap<>()).build(); importCustomers.forEach(importCustomer -> { // 校验,判断是否有不符合的原因 - // TODO @puhui999:可以用 ValidationUtils 做参数校验;可能要封装一个方法,返回 message;这样的话,就可以在 CrmCustomerImportExcelVO 写需要校验的参数啦; try { validateCustomerForCreate(importCustomer); } catch (ServiceException ex) { @@ -306,13 +322,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { LogRecordContext.putVariable("isUpdate", isUpdate); } - private void validateCustomerForCreate(CrmCustomerImportExcelVO importCustomer) { - // 校验客户名称不能为空 - if (StrUtil.isEmptyIfStr(importCustomer.getName())) { - throw exception(CUSTOMER_CREATE_NAME_NOT_NULL); - } - } - // ==================== 公海相关操作 ==================== @Override @@ -341,18 +350,14 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { @Override @Transactional(rollbackFor = Exception.class) public void receiveCustomer(List ids, Long ownerUserId, Boolean isReceive) { - if (!isReceive && !CrmPermissionUtils.isCrmAdmin()) { // 只有管理员可以分配 - throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.CRM_CUSTOMER.getName()); - } - // 1.1 校验存在 List customers = customerMapper.selectBatchIds(ids); if (customers.size() != ids.size()) { throw exception(CUSTOMER_NOT_EXISTS); } - // 1.2. 校验负责人是否存在 + // 1.2 校验负责人是否存在 adminUserApi.validateUserList(singletonList(ownerUserId)); - // 1.3. 校验状态 + // 1.3 校验状态 customers.forEach(customer -> { // 校验是否已有负责人 validateCustomerOwnerExists(customer, false); @@ -364,12 +369,13 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { // 1.4 校验负责人是否到达上限 validateCustomerExceedOwnerLimit(ownerUserId, customers.size()); - // 2.1 领取公海数据 + // 2. 领取公海数据 List updateCustomers = new ArrayList<>(); List createPermissions = new ArrayList<>(); customers.forEach(customer -> { // 2.1. 设置负责人 - updateCustomers.add(new CrmCustomerDO().setId(customer.getId()).setOwnerUserId(ownerUserId)); + updateCustomers.add(new CrmCustomerDO().setId(customer.getId()) + .setOwnerUserId(ownerUserId).setOwnerTime(LocalDateTime.now())); // 2.2. 创建负责人数据权限 createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) .setBizId(customer.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); @@ -396,45 +402,36 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { if (poolConfig == null || !poolConfig.getEnabled()) { return 0; } - // 1.1 获取没有锁定的不在公海的客户 - List customerList = customerMapper.selectListByLockAndNotPool(Boolean.FALSE); - // TODO @puhui999:下面也搞到 sql 里去哈;写 or 查询,问题不大的;低 393 到 402;原因是,避免无用的太多数据查询到 java 进程里; - List poolCustomerList = new ArrayList<>(); - poolCustomerList.addAll(filterList(customerList, customer -> - !customer.getDealStatus() && (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0)); - poolCustomerList.addAll(filterList(customerList, customer -> { - if (!customer.getDealStatus()) { // 这里只处理成交的 - return false; - } - LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime()); - return (poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime)) <= 0; - })); - + // 1. 获得需要放到的客户列表 + List customerList = customerMapper.selectListByAutoPool(poolConfig); // 2. 逐个放入公海 int count = 0; - for (CrmCustomerDO customer : poolCustomerList) { + for (CrmCustomerDO customer : customerList) { try { getSelf().putCustomerPool(customer); count++; } catch (Throwable e) { - log.error("[autoPutCustomerPool][Customer 客户({}) 放入公海异常]", customer.getId(), e); + log.error("[autoPutCustomerPool][客户({}) 放入公海异常]", customer.getId(), e); } } return count; } - private void putCustomerPool(CrmCustomerDO customer) { + @Transactional(rollbackFor = Exception.class) // 需要 protected 修饰,因为需要在事务中调用 + protected void putCustomerPool(CrmCustomerDO customer) { // 1. 设置负责人为 NULL int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null); if (updateOwnerUserIncr == 0) { throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL); } - // 2. 删除负责人数据权限 + + // 2. 联系人的负责人,也要设置为 null。因为:因为领取后,负责人也要关联过来,这块和 receiveCustomer 是对应的 + contactService.updateOwnerUserIdByCustomerId(customer.getId(), null); + + // 3. 删除负责人数据权限 + // 注意:需要放在 contactService 后面,不然【客户】数据权限已经被删除,无法操作! permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(), CrmPermissionLevelEnum.OWNER.getLevel()); - - // 3. 联系人的负责人,也要设置为 null。因为:因为领取后,负责人也要关联过来,这块和 receiveCustomer 是对应的 - contactService.updateOwnerUserIdByCustomerId(customer.getId(), null); } @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_RECEIVE_SUB_TYPE, bizNo = "{{#customer.id}}", @@ -466,22 +463,76 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { return customerMapper.selectPage(pageReqVO, userId); } - public PageResult getPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO, - CrmCustomerPoolConfigDO poolConfigDO, - Long userId) { - return customerMapper.selectPutInPoolRemindCustomerPage(pageReqVO, poolConfigDO, userId); + @Override + public PageResult getPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, Long userId) { + CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); + if (ObjUtil.isNull(poolConfig) + || Boolean.FALSE.equals(poolConfig.getEnabled()) + || Boolean.FALSE.equals(poolConfig.getNotifyEnabled())) { + return PageResult.empty(); + } + return customerMapper.selectPutPoolRemindCustomerPage(pageVO, poolConfig, userId); + } + + @Override + public Long getPutPoolRemindCustomerCount(Long userId) { + CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); + if (ObjUtil.isNull(poolConfig) + || Boolean.FALSE.equals(poolConfig.getEnabled()) + || Boolean.FALSE.equals(poolConfig.getNotifyEnabled())) { + return 0L; + } + CrmCustomerPageReqVO pageVO = new CrmCustomerPageReqVO() + .setPool(null) + .setContactStatus(CrmCustomerPageReqVO.CONTACT_TODAY) + .setSceneType(CrmSceneTypeEnum.OWNER.getType()); + return customerMapper.selectPutPoolRemindCustomerCount(pageVO, poolConfig, userId); + } + + @Override + public Long getTodayContactCustomerCount(Long userId) { + return customerMapper.selectCountByTodayContact(userId); + } + + @Override + public Long getFollowCustomerCount(Long userId) { + return customerMapper.selectCountByFollow(userId); } // ======================= 校验相关 ======================= + private void validateCustomerForCreate(CrmCustomerImportExcelVO importCustomer) { + // 校验客户名称不能为空 + if (StrUtil.isEmptyIfStr(importCustomer.getName())) { + throw exception(CUSTOMER_CREATE_NAME_NOT_NULL); + } + } + + /** + * 校验客户是否被引用 + * + * @param id 客户编号 + */ + private void validateCustomerReference(Long id) { + if (contactService.getContactCountByCustomerId(id) > 0) { + throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_CONTACT.getName()); + } + if (businessService.getBusinessCountByCustomerId(id) > 0) { + throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_BUSINESS.getName()); + } + if (contractService.getContractCountByCustomerId(id) > 0) { + throw exception(CUSTOMER_DELETE_FAIL_HAVE_REFERENCE, CrmBizTypeEnum.CRM_CONTRACT.getName()); + } + } + /** * 校验客户是否存在 * - * @param customerId 客户 id + * @param id 客户 id */ @Override - public void validateCustomer(Long customerId) { - validateCustomerExists(customerId); + public void validateCustomer(Long id) { + validateCustomerExists(id); } private void validateCustomerOwnerExists(CrmCustomerDO customer, Boolean pool) { @@ -559,7 +610,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { } } - /** * 获得自身的代理对象,解决 AOP 生效问题 * diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/bo/CrmCustomerCreateReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/bo/CrmCustomerCreateReqBO.java index 6d80b0e4c..58d1476c8 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/bo/CrmCustomerCreateReqBO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/bo/CrmCustomerCreateReqBO.java @@ -64,10 +64,6 @@ public class CrmCustomerCreateReqBO { */ @Telephone private String telephone; - /** - * 网址 - */ - private String website; /** * QQ */ diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java index 88f0b887c..31c62d32a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java @@ -17,20 +17,16 @@ import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.followup.bo.CrmFollowUpCreateReqBO; -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import jakarta.annotation.Resource; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import java.time.LocalDateTime; import java.util.Collection; -import java.util.Collections; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_DELETE_DENIED; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_NOT_EXISTS; @@ -68,41 +64,36 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService { @Override @CrmPermission(bizTypeValue = "#createReqVO.bizType", bizId = "#createReqVO.bizId", level = CrmPermissionLevelEnum.WRITE) public Long createFollowUpRecord(CrmFollowUpRecordSaveReqVO createReqVO) { - // 创建更进记录 - CrmFollowUpRecordDO followUpRecord = BeanUtils.toBean(createReqVO, CrmFollowUpRecordDO.class); - crmFollowUpRecordMapper.insert(followUpRecord); + // 1. 创建更进记录 + CrmFollowUpRecordDO record = BeanUtils.toBean(createReqVO, CrmFollowUpRecordDO.class); + crmFollowUpRecordMapper.insert(record); - LocalDateTime now = LocalDateTime.now(); - CrmUpdateFollowUpReqBO updateFollowUpReqBO = new CrmUpdateFollowUpReqBO().setBizId(followUpRecord.getBizId()) - .setContactLastTime(now).setContactNextTime(followUpRecord.getNextTime()).setContactLastContent(followUpRecord.getContent()); - // 2. 更新 bizId 对应的记录; - if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_BUSINESS.getType(), followUpRecord.getBizType())) { // 更新商机跟进信息 - businessService.updateBusinessFollowUpBatch(Collections.singletonList(updateFollowUpReqBO)); + // 2. 更新 bizId 对应的记录 + if (ObjUtil.equal(CrmBizTypeEnum.CRM_CUSTOMER.getType(), record.getBizType())) { // 更新客户跟进信息 + customerService.updateCustomerFollowUp(record.getBizId(), record.getNextTime(), record.getContent()); } - if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_LEADS.getType(), followUpRecord.getBizType())) { // 更新线索跟进信息 - clueService.updateClueFollowUp(updateFollowUpReqBO); + if (ObjUtil.equal(CrmBizTypeEnum.CRM_BUSINESS.getType(), record.getBizType())) { // 更新商机跟进信息 + businessService.updateBusinessFollowUp(record.getBizId(), record.getNextTime(), record.getContent()); } - if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CONTACT.getType(), followUpRecord.getBizType())) { // 更新联系人跟进信息 - contactService.updateContactFollowUpBatch(Collections.singletonList(updateFollowUpReqBO)); + if (ObjUtil.equal(CrmBizTypeEnum.CRM_CLUE.getType(), record.getBizType())) { // 更新线索跟进信息 + clueService.updateClueFollowUp(record.getBizId(), record.getNextTime(), record.getContent()); } - if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CONTRACT.getType(), followUpRecord.getBizType())) { // 更新合同跟进信息 - contractService.updateContractFollowUp(updateFollowUpReqBO); + if (ObjUtil.equal(CrmBizTypeEnum.CRM_CONTACT.getType(), record.getBizType())) { // 更新联系人跟进信息 + contactService.updateContactFollowUp(record.getBizId(), record.getNextTime(), record.getContent()); } - if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CUSTOMER.getType(), followUpRecord.getBizType())) { // 更新客户跟进信息 - customerService.updateCustomerFollowUp(updateFollowUpReqBO); + if (ObjUtil.equal(CrmBizTypeEnum.CRM_CONTRACT.getType(), record.getBizType())) { // 更新合同跟进信息 + contractService.updateContractFollowUp(record.getBizId(), record.getNextTime(), record.getContent()); } - // 3.1 更新 contactIds 对应的记录,不更新 lastTime 和 lastContent + // 3.1 更新 contactIds 对应的记录,只更新 nextTime if (CollUtil.isNotEmpty(createReqVO.getContactIds())) { - contactService.updateContactFollowUpBatch(convertList(createReqVO.getContactIds(), - contactId -> updateFollowUpReqBO.setBizId(contactId).setContactLastTime(null).setContactLastContent(null))); + contactService.updateContactContactNextTime(createReqVO.getContactIds(), createReqVO.getNextTime()); } - // 3.2 需要更新 businessIds 对应的记录,不更新 lastTime 和 lastContent + // 3.2 需要更新 businessIds 对应的记录,只更新 nextTime if (CollUtil.isNotEmpty(createReqVO.getBusinessIds())) { - businessService.updateBusinessFollowUpBatch(convertList(createReqVO.getBusinessIds(), - businessId -> updateFollowUpReqBO.setBizId(businessId).setContactLastTime(null).setContactLastContent(null))); + businessService.updateBusinessContactNextTime(createReqVO.getBusinessIds(), createReqVO.getNextTime()); } - return followUpRecord.getId(); + return record.getId(); } @Override diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java deleted file mode 100644 index 57f1849eb..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java +++ /dev/null @@ -1,34 +0,0 @@ -package cn.iocoder.yudao.module.crm.service.followup.bo; - -import com.mzt.logapi.starter.annotation.DiffLogField; -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotNull; -import lombok.Data; - -import java.time.LocalDateTime; - -/** - * 跟进信息 Update Req BO - * - * @author HUIHUI - */ -@Data -public class CrmUpdateFollowUpReqBO { - - @Schema(description = "数据编号", example = "3167") - @NotNull(message = "数据编号不能为空") - private Long bizId; - - @Schema(description = "最后跟进时间") - @DiffLogField(name = "最后跟进时间") - private LocalDateTime contactLastTime; - - @Schema(description = "下次联系时间") - @DiffLogField(name = "下次联系时间") - private LocalDateTime contactNextTime; - - @Schema(description = "最后更进内容") - @DiffLogField(name = "最后更进内容") - private String contactLastContent; - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogService.java deleted file mode 100644 index f13514c25..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogService.java +++ /dev/null @@ -1,23 +0,0 @@ -package cn.iocoder.yudao.module.crm.service.message; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.backlog.vo.CrmTodayCustomerPageReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import jakarta.validation.Valid; - -/** - * CRM 待办消息 Service 接口 - * - * @author dhb52 - */ -public interface CrmBacklogService { - - /** - * 根据【联系状态】、【场景类型】筛选客户分页 - * - * @param pageReqVO 分页查询 - * @return 分页数据 - */ - PageResult getTodayCustomerPage(@Valid CrmTodayCustomerPageReqVO pageReqVO, Long userId); - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogServiceImpl.java deleted file mode 100644 index 5c2417266..000000000 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/message/CrmBacklogServiceImpl.java +++ /dev/null @@ -1,28 +0,0 @@ -package cn.iocoder.yudao.module.crm.service.message; - -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.backlog.vo.CrmTodayCustomerPageReqVO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; -import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; -import jakarta.annotation.Resource; -import org.springframework.stereotype.Component; -import org.springframework.validation.annotation.Validated; - -/** - * 待办消息 Service 实现类 - * - * @author dhb52 - */ -@Component -@Validated -public class CrmBacklogServiceImpl implements CrmBacklogService { - - @Resource - private CrmCustomerMapper customerMapper; - - @Override - public PageResult getTodayCustomerPage(CrmTodayCustomerPageReqVO pageReqVO, Long userId) { - return customerMapper.selectTodayCustomerPage(pageReqVO, userId); - } - -} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java index ba3e50c6d..7191df880 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java @@ -2,16 +2,16 @@ package cn.iocoder.yudao.module.crm.service.permission; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; -import cn.iocoder.yudao.module.crm.convert.permission.CrmPermissionConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; import cn.iocoder.yudao.module.crm.dal.mysql.permission.CrmPermissionMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; -import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; +import cn.iocoder.yudao.module.crm.util.CrmPermissionUtils; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import jakarta.annotation.Resource; import org.springframework.stereotype.Service; @@ -74,8 +74,9 @@ public class CrmPermissionServiceImpl implements CrmPermissionService { // 1. 校验存在 validatePermissionExists(updateReqVO.getIds()); // 2. 更新 - List updateDO = CrmPermissionConvert.INSTANCE.convertList(updateReqVO); - permissionMapper.updateBatch(updateDO); + List updateList = CollectionUtils.convertList(updateReqVO.getIds(), + id -> new CrmPermissionDO().setId(id).setLevel(updateReqVO.getLevel())); + permissionMapper.updateBatch(updateList); } private void validatePermissionExists(Collection ids) { diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionTransferReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionTransferReqBO.java index 0e5933c0c..52f8a977a 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionTransferReqBO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/bo/CrmPermissionTransferReqBO.java @@ -3,9 +3,11 @@ package cn.iocoder.yudao.module.crm.service.permission.bo; import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import lombok.AllArgsConstructor; import lombok.Data; import jakarta.validation.constraints.NotNull; +import lombok.NoArgsConstructor; /** * 数据权限转移 Request BO @@ -13,6 +15,8 @@ import jakarta.validation.constraints.NotNull; * @author HUIHUI */ @Data +@NoArgsConstructor +@AllArgsConstructor public class CrmPermissionTransferReqBO { /** diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryService.java index 56974da11..c68c0cf77 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductCategoryService.java @@ -3,10 +3,13 @@ package cn.iocoder.yudao.module.crm.service.product; import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.product.vo.category.CrmProductCategoryListReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductCategoryDO; - import jakarta.validation.Valid; + import java.util.Collection; import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * CRM 产品分类 Service 接口 @@ -61,4 +64,14 @@ public interface CrmProductCategoryService { */ List getProductCategoryList(Collection ids); + /** + * 获得产品分类 Map + * + * @param ids 编号数组 + * @return 产品分类 Map + */ + default Map getProductCategoryMap(Collection ids) { + return convertMap(getProductCategoryList(ids), CrmProductCategoryDO::getId); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java index 6d2dd4943..9379734ef 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductService.java @@ -8,6 +8,9 @@ import jakarta.validation.Valid; import java.util.Collection; import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * CRM 产品 Service 接口 @@ -54,28 +57,46 @@ public interface CrmProductService { */ List getProductList(Collection ids); + /** + * 获得产品 Map + * + * @param ids 编号 + * @return 产品 Map + */ + default Map getProductMap(Collection ids) { + return convertMap(getProductList(ids), CrmProductDO::getId); + } + /** * 获得产品分页 * * @param pageReqVO 分页查询 * @return 产品分页 */ - PageResult getProductPage(CrmProductPageReqVO pageReqVO, Long userId); + PageResult getProductPage(CrmProductPageReqVO pageReqVO); /** - * 获得产品 + * 获得产品数量 * * @param categoryId 分类编号 * @return 产品 */ - CrmProductDO getProductByCategoryId(Long categoryId); + Long getProductByCategoryId(Long categoryId); /** - * 获得产品列表 + * 获得指定状态的产品列表 * - * @param ids 产品编号 + * @param status 状态 * @return 产品列表 */ - List getProductListByIds(Collection ids); + List getProductListByStatus(Integer status); + + /** + * 校验产品们的有效性 + * + * @param ids 编号数组 + * @return 产品列表 + */ + List validProductList(Collection ids); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java index 95205524e..c44bc6b9e 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/product/CrmProductServiceImpl.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.module.crm.service.product; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.collection.ListUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.product.vo.product.CrmProductPageReqVO; @@ -15,7 +15,6 @@ import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPerm import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; -import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; @@ -27,8 +26,10 @@ import org.springframework.validation.annotation.Validated; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; @@ -138,25 +139,41 @@ public class CrmProductServiceImpl implements CrmProductService { } @Override - public List getProductList(Collection ids) { + public PageResult getProductPage(CrmProductPageReqVO pageReqVO) { + return productMapper.selectPage(pageReqVO); + } + + @Override + public Long getProductByCategoryId(Long categoryId) { + return productMapper.selectCountByCategoryId(categoryId); + } + + @Override + public List getProductListByStatus(Integer status) { + return productMapper.selectListByStatus(status); + } + + @Override + public List validProductList(Collection ids) { if (CollUtil.isEmpty(ids)) { - return ListUtil.empty(); + return Collections.emptyList(); } - return productMapper.selectBatchIds(ids); + List list = productMapper.selectBatchIds(ids); + Map productMap = convertMap(list, CrmProductDO::getId); + for (Long id : ids) { + CrmProductDO product = productMap.get(id); + if (productMap.get(id) == null) { + throw exception(PRODUCT_NOT_EXISTS); + } + if (CommonStatusEnum.isDisable(product.getStatus())) { + throw exception(PRODUCT_NOT_ENABLE, product.getName()); + } + } + return list; } @Override - public PageResult getProductPage(CrmProductPageReqVO pageReqVO, Long userId) { - return productMapper.selectPage(pageReqVO, userId); - } - - @Override - public CrmProductDO getProductByCategoryId(Long categoryId) { - return productMapper.selectOne(new LambdaQueryWrapper().eq(CrmProductDO::getCategoryId, categoryId)); - } - - @Override - public List getProductListByIds(Collection ids) { + public List getProductList(Collection ids) { if (CollUtil.isEmpty(ids)) { return Collections.emptyList(); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java index ded059b28..b5d3cc22c 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanService.java @@ -1,9 +1,8 @@ package cn.iocoder.yudao.module.crm.service.receivable; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; import jakarta.validation.Valid; @@ -24,14 +23,22 @@ public interface CrmReceivablePlanService { * @param createReqVO 创建信息 * @return 编号 */ - Long createReceivablePlan(@Valid CrmReceivablePlanCreateReqVO createReqVO, Long userId); + Long createReceivablePlan(@Valid CrmReceivablePlanSaveReqVO createReqVO); /** * 更新回款计划 * * @param updateReqVO 更新信息 */ - void updateReceivablePlan(@Valid CrmReceivablePlanUpdateReqVO updateReqVO); + void updateReceivablePlan(@Valid CrmReceivablePlanSaveReqVO updateReqVO); + + /** + * 更新回款计划关联的回款编号 + * + * @param id 编号 + * @param receivableId 回款编号 + */ + void updateReceivablePlanReceivableId(Long id, Long receivableId); /** * 删除回款计划 @@ -77,4 +84,12 @@ public interface CrmReceivablePlanService { */ PageResult getReceivablePlanPageByCustomerId(CrmReceivablePlanPageReqVO pageReqVO); + /** + * 获得待回款提醒数量 + * + * @param userId 用户编号 + * @return 提醒数量 + */ + Long getReceivablePlanRemindCount(Long userId); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java index 9ac2e7f43..93ecb4746 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivablePlanServiceImpl.java @@ -2,41 +2,38 @@ package cn.iocoder.yudao.module.crm.service.receivable; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; -import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanUpdateReqVO; -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivablePlanConvert; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.plan.CrmReceivablePlanSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivablePlanMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; -import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; import jakarta.annotation.Resource; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.util.Collection; import java.util.List; +import java.util.Objects; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.RECEIVABLE_PLAN_NOT_EXISTS; +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.RECEIVABLE_PLAN_UPDATE_FAIL; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; -// TODO @liuhongfeng:参考 CrmReceivableServiceImpl 写的 todo 哈; - /** * 回款计划 Service 实现类 * @@ -50,27 +47,35 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { private CrmReceivablePlanMapper receivablePlanMapper; @Resource - private CrmContractService contractService; + @Lazy // 延迟加载,避免循环依赖 + private CrmReceivableService receivableService; @Resource - private CrmCustomerService customerService; + private CrmContractService contractService; @Resource private CrmPermissionService permissionService; + @Resource + private AdminUserApi adminUserApi; + @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_CREATE_SUB_TYPE, bizNo = "{{#receivablePlan.id}}", success = CRM_RECEIVABLE_PLAN_CREATE_SUCCESS) - public Long createReceivablePlan(CrmReceivablePlanCreateReqVO createReqVO, Long userId) { - // TODO @liuhongfeng:第几期的计算;基于是 contractId + contractDO 的第几个还款 - // TODO @liuhongfeng contractId:校验合同是否存在 - // 插入 - CrmReceivablePlanDO receivablePlan = CrmReceivablePlanConvert.INSTANCE.convert(createReqVO); - receivablePlan.setFinishStatus(false); - - checkReceivablePlan(receivablePlan); + public Long createReceivablePlan(CrmReceivablePlanSaveReqVO createReqVO) { + // 1. 校验关联数据是否存在 + validateRelationDataExists(createReqVO); + // 2. 插入回款计划 + CrmReceivablePlanDO maxPeriodReceivablePlan = receivablePlanMapper.selectMaxPeriodByContractId(createReqVO.getContractId()); + int period = maxPeriodReceivablePlan == null ? 1 : maxPeriodReceivablePlan.getPeriod() + 1; + CrmReceivablePlanDO receivablePlan = BeanUtils.toBean(createReqVO, CrmReceivablePlanDO.class).setPeriod(period); + if (createReqVO.getReturnTime() != null && createReqVO.getRemindDays() != null) { + receivablePlan.setRemindTime(createReqVO.getReturnTime().minusDays(createReqVO.getRemindDays())); + } receivablePlanMapper.insert(receivablePlan); - // 创建数据权限 - permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(userId) + + // 3. 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setUserId(createReqVO.getOwnerUserId()) .setBizType(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType()).setBizId(receivablePlan.getId()) .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); @@ -79,54 +84,69 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { return receivablePlan.getId(); } - private void checkReceivablePlan(CrmReceivablePlanDO receivablePlan) { - - if (ObjectUtil.isNull(receivablePlan.getContractId())) { - throw exception(CONTRACT_NOT_EXISTS); - } - - CrmContractDO contract = contractService.getContract(receivablePlan.getContractId()); - if (ObjectUtil.isNull(contract)) { - throw exception(CONTRACT_NOT_EXISTS); - } - - CrmCustomerDO customer = customerService.getCustomer(receivablePlan.getCustomerId()); - if (ObjectUtil.isNull(customer)) { - throw exception(CUSTOMER_NOT_EXISTS); - } - - } - @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", success = CRM_RECEIVABLE_PLAN_UPDATE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) - public void updateReceivablePlan(CrmReceivablePlanUpdateReqVO updateReqVO) { - // TODO @liuhongfeng:如果已经有对应的还款,则不允许编辑; - // 校验存在 + public void updateReceivablePlan(CrmReceivablePlanSaveReqVO updateReqVO) { + updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null); // 防止修改这些字段 + // 1.1 校验存在 + validateRelationDataExists(updateReqVO); + // 1.2 校验关联数据是否存在 CrmReceivablePlanDO oldReceivablePlan = validateReceivablePlanExists(updateReqVO.getId()); + // 1.3 如果已经有对应的回款,则不允许编辑 + if (Objects.nonNull(oldReceivablePlan.getReceivableId())) { + throw exception(RECEIVABLE_PLAN_UPDATE_FAIL); + } - // 更新 - CrmReceivablePlanDO updateObj = CrmReceivablePlanConvert.INSTANCE.convert(updateReqVO); + // 2. 更新回款计划 + CrmReceivablePlanDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivablePlanDO.class); + if (updateReqVO.getReturnTime() != null && updateReqVO.getRemindDays() != null) { + updateObj.setRemindTime(updateReqVO.getReturnTime().minusDays(updateReqVO.getRemindDays())); + } receivablePlanMapper.updateById(updateObj); // 3. 记录操作日志上下文 - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanUpdateReqVO.class)); + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivablePlan, CrmReceivablePlanSaveReqVO.class)); LogRecordContext.putVariable("receivablePlan", oldReceivablePlan); } + private void validateRelationDataExists(CrmReceivablePlanSaveReqVO reqVO) { + // 校验负责人存在 + if (reqVO.getOwnerUserId() != null) { + adminUserApi.validateUser(reqVO.getOwnerUserId()); + } + // 校验合同存在 + if (reqVO.getContractId() != null) { + CrmContractDO contract = contractService.getContract(reqVO.getContractId()); + reqVO.setCustomerId(contract.getCustomerId()); + } + } + @Override + public void updateReceivablePlanReceivableId(Long id, Long receivableId) { + // 校验存在 + validateReceivablePlanExists(id); + // 更新回款计划 + receivablePlanMapper.updateById(new CrmReceivablePlanDO().setId(id).setReceivableId(receivableId)); + } + + @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_PLAN_TYPE, subType = CRM_RECEIVABLE_PLAN_DELETE_SUB_TYPE, bizNo = "{{#id}}", success = CRM_RECEIVABLE_PLAN_DELETE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE_PLAN, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteReceivablePlan(Long id) { - // 校验存在 + // 1. 校验存在 CrmReceivablePlanDO receivablePlan = validateReceivablePlanExists(id); - // 删除 + + // 2. 删除 receivablePlanMapper.deleteById(id); - // 删除数据权限 + // 3. 删除数据权限 permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); - // 记录操作日志上下文 + + // 4. 记录操作日志上下文 LogRecordContext.putVariable("receivablePlan", receivablePlan); } @@ -163,4 +183,9 @@ public class CrmReceivablePlanServiceImpl implements CrmReceivablePlanService { return receivablePlanMapper.selectPageByCustomerId(pageReqVO); } + @Override + public Long getReceivablePlanRemindCount(Long userId) { + return receivablePlanMapper.selectReceivablePlanCountByRemind(userId); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java index 8e9cfa0ea..c0ca645b2 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableService.java @@ -1,15 +1,18 @@ package cn.iocoder.yudao.module.crm.service.receivable; import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; import jakarta.validation.Valid; +import java.math.BigDecimal; import java.util.Collection; import java.util.List; +import java.util.Map; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; /** * CRM 回款 Service 接口 @@ -22,17 +25,24 @@ public interface CrmReceivableService { * 创建回款 * * @param createReqVO 创建信息 - * @param userId 用户编号 * @return 编号 */ - Long createReceivable(@Valid CrmReceivableCreateReqVO createReqVO, Long userId); + Long createReceivable(@Valid CrmReceivableSaveReqVO createReqVO); /** * 更新回款 * * @param updateReqVO 更新信息 */ - void updateReceivable(@Valid CrmReceivableUpdateReqVO updateReqVO); + void updateReceivable(@Valid CrmReceivableSaveReqVO updateReqVO); + + /** + * 更新回款流程审批结果 + * + * @param id 回款编号 + * @param bpmResult BPM 审批结果 + */ + void updateReceivableAuditStatus(Long id, Integer bpmResult); /** * 删除回款 @@ -41,6 +51,14 @@ public interface CrmReceivableService { */ void deleteReceivable(Long id); + /** + * 发起回款审批流程 + * + * @param id 回款编号 + * @param userId 用户编号 + */ + void submitReceivable(Long id, Long userId); + /** * 获得回款 * @@ -57,6 +75,16 @@ public interface CrmReceivableService { */ List getReceivableList(Collection ids); + /** + * 获得回款 Map + * + * @param ids 编号 + * @return 回款 Map + */ + default Map getReceivableMap(Collection ids) { + return convertMap(getReceivableList(ids), CrmReceivableDO::getId); + } + /** * 获得回款分页 * @@ -78,4 +106,20 @@ public interface CrmReceivableService { */ PageResult getReceivablePageByCustomerId(CrmReceivablePageReqVO pageReqVO); + /** + * 获得待审核回款数量 + * + * @param userId 用户编号 + * @return 待审批数量 + */ + Long getAuditReceivableCount(Long userId); + + /** + * 获得合同已回款金额 Map + * + * @param contractIds 合同编号 + * @return 回款金额 Map + */ + Map getReceivablePriceMapByContractId(Collection contractIds); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java index effe0d720..7ef96effa 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/CrmReceivableServiceImpl.java @@ -2,41 +2,50 @@ package cn.iocoder.yudao.module.crm.service.receivable; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableCreateReqVO; +import cn.iocoder.yudao.framework.common.util.object.ObjectUtils; +import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi; +import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO; import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivablePageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableUpdateReqVO; -import cn.iocoder.yudao.module.crm.convert.receivable.CrmReceivableConvert; +import cn.iocoder.yudao.module.crm.controller.admin.receivable.vo.receivable.CrmReceivableSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivableDO; import cn.iocoder.yudao.module.crm.dal.dataobject.receivable.CrmReceivablePlanDO; import cn.iocoder.yudao.module.crm.dal.mysql.receivable.CrmReceivableMapper; +import cn.iocoder.yudao.module.crm.dal.redis.no.CrmNoRedisDAO; import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; -import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; +import java.math.BigDecimal; +import java.util.Arrays; import java.util.Collection; import java.util.List; +import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; +import static cn.iocoder.yudao.module.crm.util.CrmAuditStatusUtils.convertBpmResultToAuditStatus; /** * CRM 回款 Service 实现类 @@ -45,111 +54,198 @@ import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; */ @Service @Validated +@Slf4j public class CrmReceivableServiceImpl implements CrmReceivableService { + /** + * BPM 合同审批流程标识 + */ + public static final String BPM_PROCESS_DEFINITION_KEY = "crm-receivable-audit"; + @Resource private CrmReceivableMapper receivableMapper; + @Resource + private CrmNoRedisDAO noRedisDAO; + @Resource private CrmContractService contractService; @Resource - private CrmCustomerService customerService; - @Resource + @Lazy // 延迟加载,避免循环依赖 private CrmReceivablePlanService receivablePlanService; @Resource private CrmPermissionService permissionService; + @Resource + private AdminUserApi adminUserApi; + @Resource + private BpmProcessInstanceApi bpmProcessInstanceApi; + + // TODO @puhui999:操作日志没记录上 @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_CREATE_SUB_TYPE, bizNo = "{{#receivable.id}}", success = CRM_RECEIVABLE_CREATE_SUCCESS) - public Long createReceivable(CrmReceivableCreateReqVO createReqVO, Long userId) { - // 插入还款 - CrmReceivableDO receivable = CrmReceivableConvert.INSTANCE.convert(createReqVO); - if (ObjectUtil.isNull(receivable.getAuditStatus())) { - receivable.setAuditStatus(CommonStatusEnum.ENABLE.getStatus()); + public Long createReceivable(CrmReceivableSaveReqVO createReqVO) { + // 1.1 校验可回款金额超过上限 + validateReceivablePriceExceedsLimit(createReqVO); + // 1.2 校验关联数据存在 + validateRelationDataExists(createReqVO); + // 1.3 生成回款编号 + String no = noRedisDAO.generate(CrmNoRedisDAO.RECEIVABLE_PREFIX); + if (receivableMapper.selectByNo(no) != null) { + throw exception(RECEIVABLE_NO_EXISTS); } - receivable.setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus()); - - // TODO @liuhongfeng:一般来说,逻辑的写法,是要先检查,后操作 db;所以,你这个 check 应该放到 CrmReceivableDO receivable 之前; - checkReceivable(receivable); + // 2.1 插入回款 + CrmReceivableDO receivable = BeanUtils.toBean(createReqVO, CrmReceivableDO.class) + .setNo(no).setAuditStatus(CrmAuditStatusEnum.DRAFT.getStatus()); receivableMapper.insert(receivable); + // 2.2 + // 3. 创建数据权限 permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_RECEIVABLE.getType()) - .setBizId(receivable.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 - // TODO @liuhongfeng:需要更新关联的 plan + .setBizId(receivable.getId()).setUserId(createReqVO.getOwnerUserId()) + .setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 - // 4. 记录操作日志上下文 + // 4. 更新关联的回款计划 + if (createReqVO.getPlanId() != null) { + receivablePlanService.updateReceivablePlanReceivableId(receivable.getPlanId(), receivable.getId()); + } + + // 5. 记录操作日志上下文 LogRecordContext.putVariable("receivable", receivable); return receivable.getId(); } - // TODO @liuhongfeng:这里的括号要注意排版; - private void checkReceivable(CrmReceivableDO receivable) { - // TODO @liuhongfeng:校验 no 的唯一性 - // TODO @liuhongfeng:这个放在参数校验合适 - if (ObjectUtil.isNull(receivable.getContractId())) { - throw exception(CONTRACT_NOT_EXISTS); + private void validateReceivablePriceExceedsLimit(CrmReceivableSaveReqVO reqVO) { + // 1. 计算剩余可退款金额,不包括 reqVO 自身 + CrmContractDO contract = contractService.validateContract(reqVO.getContractId()); + List receivables = receivableMapper.selectListByContractIdAndStatus(reqVO.getContractId(), + Arrays.asList(CrmAuditStatusEnum.APPROVE.getStatus(), CrmAuditStatusEnum.PROCESS.getStatus())); + if (reqVO.getId() != null) { + receivables.removeIf(receivable -> ObjectUtil.equal(receivable.getId(), reqVO.getId())); } - - CrmContractDO contract = contractService.getContract(receivable.getContractId()); - if (ObjectUtil.isNull(contract)) { - throw exception(CONTRACT_NOT_EXISTS); + BigDecimal notReceivablePrice = contract.getTotalPrice().subtract( + CollectionUtils.getSumValue(receivables, CrmReceivableDO::getPrice, BigDecimal::add, BigDecimal.ZERO)); + // 2. 校验金额是否超过 + if (reqVO.getPrice().compareTo(notReceivablePrice) > 0) { + throw exception(RECEIVABLE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT, notReceivablePrice); } - - CrmCustomerDO customer = customerService.getCustomer(receivable.getCustomerId()); - if (ObjectUtil.isNull(customer)) { - throw exception(CUSTOMER_NOT_EXISTS); - } - - CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(receivable.getPlanId()); - if (ObjectUtil.isNull(receivablePlan)) { - throw exception(RECEIVABLE_PLAN_NOT_EXISTS); - } - } + private void validateRelationDataExists(CrmReceivableSaveReqVO reqVO) { + if (reqVO.getOwnerUserId() != null) { + adminUserApi.validateUser(reqVO.getOwnerUserId()); // 校验负责人存在 + } + if (reqVO.getContractId() != null) { + CrmContractDO contract = contractService.validateContract(reqVO.getContractId()); + if (ObjectUtil.notEqual(contract.getAuditStatus(), CrmAuditStatusEnum.APPROVE.getStatus())) { + throw exception(RECEIVABLE_CREATE_FAIL_CONTRACT_NOT_APPROVE); + } + reqVO.setCustomerId(contract.getCustomerId()); // 设置客户编号 + } + if (reqVO.getPlanId() != null) { + CrmReceivablePlanDO receivablePlan = receivablePlanService.getReceivablePlan(reqVO.getPlanId()); + if (receivablePlan == null) { + throw exception(RECEIVABLE_PLAN_NOT_EXISTS); + } + if (receivablePlan.getReceivableId() != null) { + throw exception(RECEIVABLE_PLAN_EXISTS_RECEIVABLE); + } + } + } + + // TODO @puhui999:操作日志没记录上 @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}", success = CRM_RECEIVABLE_UPDATE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE) - public void updateReceivable(CrmReceivableUpdateReqVO updateReqVO) { - // 校验存在 - CrmReceivableDO oldReceivable = validateReceivableExists(updateReqVO.getId()); - // TODO @liuhongfeng:只有在草稿、审核中,可以提交修改 + public void updateReceivable(CrmReceivableSaveReqVO updateReqVO) { + Assert.notNull(updateReqVO.getId(), "回款编号不能为空"); + updateReqVO.setOwnerUserId(null).setCustomerId(null).setContractId(null).setPlanId(null); // 不允许修改的字段 + // 1.1 校验可回款金额超过上限 + validateReceivablePriceExceedsLimit(updateReqVO); + // 1.2 校验存在 + CrmReceivableDO receivable = validateReceivableExists(updateReqVO.getId()); + // 1.3 只有草稿、审批中,可以编辑; + if (!ObjectUtils.equalsAny(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus(), + CrmAuditStatusEnum.PROCESS.getStatus())) { + throw exception(RECEIVABLE_UPDATE_FAIL_EDITING_PROHIBITED); + } - // 更新还款 - CrmReceivableDO updateObj = CrmReceivableConvert.INSTANCE.convert(updateReqVO); + // 2. 更新回款 + CrmReceivableDO updateObj = BeanUtils.toBean(updateReqVO, CrmReceivableDO.class); receivableMapper.updateById(updateObj); - // TODO @liuhongfeng:需要更新关联的 plan // 3. 记录操作日志上下文 - LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldReceivable, CrmReceivableUpdateReqVO.class)); - LogRecordContext.putVariable("receivable", oldReceivable); + LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(receivable, CrmReceivableSaveReqVO.class)); + LogRecordContext.putVariable("receivable", receivable); } - // TODO @liuhongfeng:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum + @Override + public void updateReceivableAuditStatus(Long id, Integer bpmResult) { + // 1.1 校验存在 + CrmReceivableDO receivable = validateReceivableExists(id); + // 1.2 只有审批中,可以更新审批结果 + if (ObjUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) { + log.error("[updateReceivableAuditStatus][receivable({}) 不处于审批中,无法更新审批结果({})]", + receivable.getId(), bpmResult); + throw exception(RECEIVABLE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS); + } - // TODO @liuhongfeng:缺一个发起审批的接口;只有草稿可以发起审批;CrmAuditStatusEnum + // 2. 更新回款审批状态 + Integer auditStatus = convertBpmResultToAuditStatus(bpmResult); + receivableMapper.updateById(new CrmReceivableDO().setId(id).setAuditStatus(auditStatus)); + } @Override + @Transactional(rollbackFor = Exception.class) @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_DELETE_SUB_TYPE, bizNo = "{{#id}}", success = CRM_RECEIVABLE_DELETE_SUCCESS) @CrmPermission(bizType = CrmBizTypeEnum.CRM_RECEIVABLE, bizId = "#id", level = CrmPermissionLevelEnum.OWNER) public void deleteReceivable(Long id) { - // TODO @liuhongfeng:如果被 CrmReceivablePlanDO 所使用,则不允许删除 - // 校验存在 + // 1.1 校验存在 CrmReceivableDO receivable = validateReceivableExists(id); - // 删除 + // 1.2 如果被 CrmReceivablePlanDO 所使用,则不允许删除 + if (receivable.getPlanId() != null && receivablePlanService.getReceivablePlan(receivable.getPlanId()) != null) { + throw exception(RECEIVABLE_DELETE_FAIL); + } + // TODO @puhui999:审批通过时,不允许删除; + + // 2. 删除 receivableMapper.deleteById(id); + // 3. 删除数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_RECEIVABLE.getType(), id); - // 删除数据权限 - permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); - - // 记录操作日志上下文 + // 4. 记录操作日志上下文 LogRecordContext.putVariable("receivable", receivable); } + @Override + @Transactional(rollbackFor = Exception.class) + @LogRecord(type = CRM_RECEIVABLE_TYPE, subType = CRM_RECEIVABLE_SUBMIT_SUB_TYPE, bizNo = "{{#id}}", + success = CRM_RECEIVABLE_SUBMIT_SUCCESS) + public void submitReceivable(Long id, Long userId) { + // 1. 校验回款是否在审批 + CrmReceivableDO receivable = validateReceivableExists(id); + if (ObjUtil.notEqual(receivable.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus())) { + throw exception(RECEIVABLE_SUBMIT_FAIL_NOT_DRAFT); + } + + // 2. 创建回款审批流程实例 + String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO() + .setProcessDefinitionKey(BPM_PROCESS_DEFINITION_KEY).setBusinessKey(String.valueOf(id))); + + // 3. 更新回款工作流编号 + receivableMapper.updateById(new CrmReceivableDO().setId(id).setProcessInstanceId(processInstanceId) + .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus())); + + // 4. 记录日志 + LogRecordContext.putVariable("receivableNo", receivable.getNo()); + } + private CrmReceivableDO validateReceivableExists(Long id) { CrmReceivableDO receivable = receivableMapper.selectById(id); if (receivable == null) { @@ -183,4 +279,14 @@ public class CrmReceivableServiceImpl implements CrmReceivableService { return receivableMapper.selectPageByCustomerId(pageReqVO); } + @Override + public Long getAuditReceivableCount(Long userId) { + return receivableMapper.selectCountByAudit(userId); + } + + @Override + public Map getReceivablePriceMapByContractId(Collection contractIds) { + return receivableMapper.selectReceivablePriceMapByContractId(contractIds); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/listener/CrmReceivableResultListener.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/listener/CrmReceivableResultListener.java new file mode 100644 index 000000000..8c4f3e974 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/listener/CrmReceivableResultListener.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.crm.service.receivable.listener; + +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEvent; +import cn.iocoder.yudao.module.bpm.event.BpmProcessInstanceResultEventListener; +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService; +import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableServiceImpl; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +/** + * 回款审批的结果的监听器实现类 + * + * @author HUIHUI + */ +@Component +public class CrmReceivableResultListener extends BpmProcessInstanceResultEventListener { + + @Resource + private CrmReceivableService receivableService; + + @Override + public String getProcessDefinitionKey() { + return CrmReceivableServiceImpl.BPM_PROCESS_DEFINITION_KEY; + } + + @Override + public void onEvent(BpmProcessInstanceResultEvent event) { + receivableService.updateReceivableAuditStatus(Long.parseLong(event.getBusinessKey()), event.getResult()); + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingService.java similarity index 52% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingService.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingService.java index 2ff28d385..c9455708c 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingService.java @@ -1,17 +1,17 @@ -package cn.iocoder.yudao.module.crm.service.bi; +package cn.iocoder.yudao.module.crm.service.statistics; -import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO; import java.util.List; /** - * CRM BI 排行榜 Service 接口 + * CRM 排行榜统计 Service 接口 * * @author anhaohao */ -public interface CrmBiRankingService { +public interface CrmStatisticsRankingService { /** * 获得合同金额排行榜 @@ -19,7 +19,7 @@ public interface CrmBiRankingService { * @param rankReqVO 排行参数 * @return 合同金额排行榜 */ - List getContractPriceRank(CrmBiRankReqVO rankReqVO); + List getContractPriceRank(CrmStatisticsRankReqVO rankReqVO); /** * 获得回款金额排行榜 @@ -27,7 +27,7 @@ public interface CrmBiRankingService { * @param rankReqVO 排行参数 * @return 回款金额排行榜 */ - List getReceivablePriceRank(CrmBiRankReqVO rankReqVO); + List getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO); /** * 获得签约合同数量排行榜 @@ -35,7 +35,7 @@ public interface CrmBiRankingService { * @param rankReqVO 排行参数 * @return 签约合同数量排行榜 */ - List getContractCountRank(CrmBiRankReqVO rankReqVO); + List getContractCountRank(CrmStatisticsRankReqVO rankReqVO); /** * 获得产品销量排行榜 @@ -43,7 +43,7 @@ public interface CrmBiRankingService { * @param rankReqVO 排行参数 * @return 产品销量排行榜 */ - List getProductSalesRank(CrmBiRankReqVO rankReqVO); + List getProductSalesRank(CrmStatisticsRankReqVO rankReqVO); /** * 获得新增客户数排行榜 @@ -51,7 +51,7 @@ public interface CrmBiRankingService { * @param rankReqVO 排行参数 * @return 新增客户数排行榜 */ - List getCustomerCountRank(CrmBiRankReqVO rankReqVO); + List getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO); /** * 获得联系人数量排行榜 @@ -59,7 +59,7 @@ public interface CrmBiRankingService { * @param rankReqVO 排行参数 * @return 联系人数量排行榜 */ - List getContactsCountRank(CrmBiRankReqVO rankReqVO); + List getContactsCountRank(CrmStatisticsRankReqVO rankReqVO); /** * 获得跟进次数排行榜 @@ -67,7 +67,7 @@ public interface CrmBiRankingService { * @param rankReqVO 排行参数 * @return 跟进次数排行榜 */ - List getFollowCountRank(CrmBiRankReqVO rankReqVO); + List getFollowCountRank(CrmStatisticsRankReqVO rankReqVO); /** * 获得跟进客户数排行榜 @@ -75,6 +75,6 @@ public interface CrmBiRankingService { * @param rankReqVO 排行参数 * @return 跟进客户数排行榜 */ - List getFollowCustomerCountRank(CrmBiRankReqVO rankReqVO); + List getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingServiceImpl.java similarity index 54% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingServiceImpl.java index 60e1b4ecb..428ec1763 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/bi/CrmBiRankingServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsRankingServiceImpl.java @@ -1,10 +1,10 @@ -package cn.iocoder.yudao.module.crm.service.bi; +package cn.iocoder.yudao.module.crm.service.statistics; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; -import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRanKRespVO; -import cn.iocoder.yudao.module.crm.controller.admin.bi.vo.CrmBiRankReqVO; -import cn.iocoder.yudao.module.crm.dal.mysql.bi.CrmBiRankingMapper; +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO; +import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRankReqVO; +import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsRankingMapper; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; @@ -23,16 +23,16 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; /** - * CRM BI 排行榜 Service 实现类 + * CRM 排行榜统计 Service 实现类 * * @author anhaohao */ @Service @Validated -public class CrmBiRankingServiceImpl implements CrmBiRankingService { +public class CrmStatisticsRankingServiceImpl implements CrmStatisticsRankingService { @Resource - private CrmBiRankingMapper biRankingMapper; + private CrmStatisticsRankingMapper rankMapper; @Resource private AdminUserApi adminUserApi; @@ -40,43 +40,43 @@ public class CrmBiRankingServiceImpl implements CrmBiRankingService { private DeptApi deptApi; @Override - public List getContractPriceRank(CrmBiRankReqVO rankReqVO) { - return getRank(rankReqVO, biRankingMapper::selectContractPriceRank); + public List getContractPriceRank(CrmStatisticsRankReqVO rankReqVO) { + return getRank(rankReqVO, rankMapper::selectContractPriceRank); } @Override - public List getReceivablePriceRank(CrmBiRankReqVO rankReqVO) { - return getRank(rankReqVO, biRankingMapper::selectReceivablePriceRank); + public List getReceivablePriceRank(CrmStatisticsRankReqVO rankReqVO) { + return getRank(rankReqVO, rankMapper::selectReceivablePriceRank); } @Override - public List getContractCountRank(CrmBiRankReqVO rankReqVO) { - return getRank(rankReqVO, biRankingMapper::selectContractCountRank); + public List getContractCountRank(CrmStatisticsRankReqVO rankReqVO) { + return getRank(rankReqVO, rankMapper::selectContractCountRank); } @Override - public List getProductSalesRank(CrmBiRankReqVO rankReqVO) { - return getRank(rankReqVO, biRankingMapper::selectProductSalesRank); + public List getProductSalesRank(CrmStatisticsRankReqVO rankReqVO) { + return getRank(rankReqVO, rankMapper::selectProductSalesRank); } @Override - public List getCustomerCountRank(CrmBiRankReqVO rankReqVO) { - return getRank(rankReqVO, biRankingMapper::selectCustomerCountRank); + public List getCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) { + return getRank(rankReqVO, rankMapper::selectCustomerCountRank); } @Override - public List getContactsCountRank(CrmBiRankReqVO rankReqVO) { - return getRank(rankReqVO, biRankingMapper::selectContactsCountRank); + public List getContactsCountRank(CrmStatisticsRankReqVO rankReqVO) { + return getRank(rankReqVO, rankMapper::selectContactsCountRank); } @Override - public List getFollowCountRank(CrmBiRankReqVO rankReqVO) { - return getRank(rankReqVO, biRankingMapper::selectFollowCountRank); + public List getFollowCountRank(CrmStatisticsRankReqVO rankReqVO) { + return getRank(rankReqVO, rankMapper::selectFollowCountRank); } @Override - public List getFollowCustomerCountRank(CrmBiRankReqVO rankReqVO) { - return getRank(rankReqVO, biRankingMapper::selectFollowCustomerCountRank); + public List getFollowCustomerCountRank(CrmStatisticsRankReqVO rankReqVO) { + return getRank(rankReqVO, rankMapper::selectFollowCustomerCountRank); } /** @@ -86,18 +86,18 @@ public class CrmBiRankingServiceImpl implements CrmBiRankingService { * @param rankFunction 排行榜方法 * @return 排行版数据 */ - private List getRank(CrmBiRankReqVO rankReqVO, Function> rankFunction) { + private List getRank(CrmStatisticsRankReqVO rankReqVO, Function> rankFunction) { // 1. 获得用户编号数组 rankReqVO.setUserIds(getUserIds(rankReqVO.getDeptId())); if (CollUtil.isEmpty(rankReqVO.getUserIds())) { return Collections.emptyList(); } // 2. 获得排行数据 - List ranks = rankFunction.apply(rankReqVO); + List ranks = rankFunction.apply(rankReqVO); if (CollUtil.isEmpty(ranks)) { return Collections.emptyList(); } - ranks.sort(Comparator.comparing(CrmBiRanKRespVO::getCount).reversed()); + ranks.sort(Comparator.comparing(CrmStatisticsRanKRespVO::getCount).reversed()); // 3. 拼接用户信息 appendUserInfo(ranks); return ranks; @@ -108,8 +108,8 @@ public class CrmBiRankingServiceImpl implements CrmBiRankingService { * * @param ranks 排行榜数据 */ - private void appendUserInfo(List ranks) { - Map userMap = adminUserApi.getUserMap(convertSet(ranks, CrmBiRanKRespVO::getOwnerUserId)); + private void appendUserInfo(List ranks) { + Map userMap = adminUserApi.getUserMap(convertSet(ranks, CrmStatisticsRanKRespVO::getOwnerUserId)); Map deptMap = deptApi.getDeptMap(convertSet(userMap.values(), AdminUserRespDTO::getDeptId)); ranks.forEach(rank -> MapUtils.findAndThen(userMap, rank.getOwnerUserId(), user -> { rank.setNickname(user.getNickname()); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmAuditStatusUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmAuditStatusUtils.java new file mode 100644 index 000000000..665e98fbe --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmAuditStatusUtils.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.crm.util; + +import cn.hutool.core.lang.Assert; +import cn.iocoder.yudao.module.bpm.enums.task.BpmProcessInstanceResultEnum; +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum; + +/** + * CRM 流程工具类 + * + * @author HUIHUI + */ +public class CrmAuditStatusUtils { + + /** + * BPM 审批结果转换 + * + * @param bpmResult BPM 审批结果 + */ + public static Integer convertBpmResultToAuditStatus(Integer bpmResult) { + Integer auditStatus = BpmProcessInstanceResultEnum.APPROVE.getResult().equals(bpmResult) ? CrmAuditStatusEnum.APPROVE.getStatus() + : BpmProcessInstanceResultEnum.REJECT.getResult().equals(bpmResult) ? CrmAuditStatusEnum.REJECT.getStatus() + : BpmProcessInstanceResultEnum.CANCEL.getResult().equals(bpmResult) ? BpmProcessInstanceResultEnum.CANCEL.getResult() : null; + Assert.notNull(auditStatus, "BPM 审批结果({}) 转换失败", bpmResult); + return auditStatus; + } + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java similarity index 78% rename from yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java rename to yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java index cfd2f8c1b..587e9ab56 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmQueryWrapperUtils.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/util/CrmPermissionUtils.java @@ -7,9 +7,10 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; -import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; +import cn.iocoder.yudao.module.system.api.permission.PermissionApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; +import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import com.github.yulichang.autoconfigure.MybatisPlusJoinProperties; import com.github.yulichang.wrapper.MPJLambdaWrapper; @@ -18,13 +19,24 @@ import java.util.Collection; import java.util.List; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; /** - * CRM 查询工具类 + * 数据权限工具类 * * @author HUIHUI */ -public class CrmQueryWrapperUtils { +public class CrmPermissionUtils { + + /** + * 校验用户是否是 CRM 管理员 + * + * @return 是/否 + */ + public static boolean isCrmAdmin() { + PermissionApi permissionApi = SpringUtil.getBean(PermissionApi.class); + return permissionApi.hasAnyRoles(getLoginUserId(), RoleCodeEnum.CRM_ADMIN.getCode()); + } /** * 构造 CRM 数据类型数据分页查询条件 @@ -38,7 +50,8 @@ public class CrmQueryWrapperUtils { */ public static , S> void appendPermissionCondition(T query, Integer bizType, SFunction bizId, Long userId, Integer sceneType, Boolean pool) { - final String ownerUserIdField = SingletonManager.getMybatisPlusJoinProperties().getTableAlias() + ".owner_user_id"; + MybatisPlusJoinProperties mybatisPlusJoinProperties = SpringUtil.getBean(MybatisPlusJoinProperties.class); + final String ownerUserIdField = mybatisPlusJoinProperties.getTableAlias() + ".owner_user_id"; // 1. 构建数据权限连表条件 if (!CrmPermissionUtils.isCrmAdmin() && ObjUtil.notEqual(pool, Boolean.TRUE)) { // 管理员,公海不需要数据权限 query.innerJoin(CrmPermissionDO.class, on -> on.eq(CrmPermissionDO::getBizType, bizType) @@ -56,7 +69,8 @@ public class CrmQueryWrapperUtils { } // 2.3 场景三:下属负责的数据 if (CrmSceneTypeEnum.isSubordinate(sceneType)) { - List subordinateUsers = SingletonManager.getAdminUserApi().getUserListBySubordinate(userId); + AdminUserApi adminUserApi = SpringUtil.getBean(AdminUserApi.class); + List subordinateUsers = adminUserApi.getUserListBySubordinate(userId); if (CollUtil.isEmpty(subordinateUsers)) { query.eq(ownerUserIdField, -1); // 不返回任何结果 } else { @@ -81,7 +95,7 @@ public class CrmQueryWrapperUtils { * @param userId 用户编号 */ public static > void appendPermissionCondition(T query, Integer bizType, Collection bizIds, Long userId) { - if (CrmPermissionUtils.isCrmAdmin()) {// 管理员不需要数据权限 + if (isCrmAdmin()) {// 管理员不需要数据权限 return; } query.innerJoin(CrmPermissionDO.class, on -> @@ -89,25 +103,4 @@ public class CrmQueryWrapperUtils { .eq(CollUtil.isNotEmpty(bizIds), CrmPermissionDO::getUserId, userId)); } - /** - * 静态内部类实现单例获取 - * - * @author HUIHUI - */ - private static class SingletonManager { - - private static final AdminUserApi ADMIN_USER_API = SpringUtil.getBean(AdminUserApi.class); - - private static final MybatisPlusJoinProperties MYBATIS_PLUS_JOIN_PROPERTIES = SpringUtil.getBean(MybatisPlusJoinProperties.class); - - public static AdminUserApi getAdminUserApi() { - return ADMIN_USER_API; - } - - public static MybatisPlusJoinProperties getMybatisPlusJoinProperties() { - return MYBATIS_PLUS_JOIN_PROPERTIES; - } - - } - } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml index 10030e0a7..c193873ce 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/bi/CrmBiRankingMapper.xml @@ -1,10 +1,10 @@ - + + resultType="cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.CrmStatisticsRanKRespVO"> SELECT COUNT(1) AS count, owner_user_id FROM crm_contract WHERE deleted = 0 @@ -64,7 +64,7 @@ + SELECT + (SELECT IFNULL(SUM(total_price), 0) + FROM erp_purchase_in + WHERE in_time >= #{beginTime} + + AND in_time < #{endTime} + + AND deleted = 0) - + (SELECT IFNULL(SUM(total_price), 0) + FROM erp_purchase_return + WHERE return_time >= #{beginTime} + + AND return_time < #{endTime} + + AND deleted = 0) + + + \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/statistics/ErpSaleStatisticsMapper.xml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/statistics/ErpSaleStatisticsMapper.xml new file mode 100644 index 000000000..8e74606c9 --- /dev/null +++ b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/statistics/ErpSaleStatisticsMapper.xml @@ -0,0 +1,23 @@ + + + + + + + \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/stock/ErpWarehouseMapper.xml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/stock/ErpWarehouseMapper.xml deleted file mode 100644 index be2bc61cb..000000000 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/stock/ErpWarehouseMapper.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/supplier/ErpSupplierMapper.xml b/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/supplier/ErpSupplierMapper.xml deleted file mode 100644 index 54849af3f..000000000 --- a/yudao-module-erp/yudao-module-erp-biz/src/main/resources/mapper/supplier/ErpSupplierMapper.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java index 471950017..19aa4e718 100644 --- a/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java +++ b/yudao-module-infra/yudao-module-infra-api/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java @@ -33,19 +33,19 @@ public interface ErrorCodeConstants { ErrorCode FILE_IS_EMPTY = new ErrorCode(1_001_003_002, "文件为空"); // ========== 代码生成器 1-001-004-000 ========== - ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1_003_001_000, "表定义已经存在"); - ErrorCode CODEGEN_IMPORT_TABLE_NULL = new ErrorCode(1_003_001_001, "导入的表不存在"); - ErrorCode CODEGEN_IMPORT_COLUMNS_NULL = new ErrorCode(1_003_001_002, "导入的字段不存在"); - ErrorCode CODEGEN_TABLE_NOT_EXISTS = new ErrorCode(1_003_001_004, "表定义不存在"); - ErrorCode CODEGEN_COLUMN_NOT_EXISTS = new ErrorCode(1_003_001_005, "字段义不存在"); - ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1_003_001_006, "同步的字段不存在"); - ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1_003_001_007, "同步失败,不存在改变"); - ErrorCode CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL = new ErrorCode(1_003_001_008, "数据库的表注释未填写"); - ErrorCode CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL = new ErrorCode(1_003_001_009, "数据库的表字段({})注释未填写"); - ErrorCode CODEGEN_MASTER_TABLE_NOT_EXISTS = new ErrorCode(1_003_001_010, "主表(id={})定义不存在,请检查"); - ErrorCode CODEGEN_SUB_COLUMN_NOT_EXISTS = new ErrorCode(1_003_001_011, "子表的字段(id={})不存在,请检查"); - ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE = new ErrorCode(1_003_001_012, "主表生成代码失败,原因:它没有子表"); - ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_COLUMN = new ErrorCode(1_003_001_013, "主表生成代码失败,原因:它的子表({})没有字段"); + ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1_001_004_002, "表定义已经存在"); + ErrorCode CODEGEN_IMPORT_TABLE_NULL = new ErrorCode(1_001_004_001, "导入的表不存在"); + ErrorCode CODEGEN_IMPORT_COLUMNS_NULL = new ErrorCode(1_001_004_002, "导入的字段不存在"); + ErrorCode CODEGEN_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_004, "表定义不存在"); + ErrorCode CODEGEN_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_005, "字段义不存在"); + ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1_001_004_006, "同步的字段不存在"); + ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1_001_004_007, "同步失败,不存在改变"); + ErrorCode CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL = new ErrorCode(1_001_004_008, "数据库的表注释未填写"); + ErrorCode CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL = new ErrorCode(1_001_004_009, "数据库的表字段({})注释未填写"); + ErrorCode CODEGEN_MASTER_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_010, "主表(id={})定义不存在,请检查"); + ErrorCode CODEGEN_SUB_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_011, "子表的字段(id={})不存在,请检查"); + ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE = new ErrorCode(1_001_004_012, "主表生成代码失败,原因:它没有子表"); + ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_COLUMN = new ErrorCode(1_001_004_013, "主表生成代码失败,原因:它的子表({})没有字段"); // ========== 文件配置 1-001-006-000 ========== ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_006_000, "文件配置不存在"); diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/MemberStatisticsController.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/MemberStatisticsController.java index ec2192608..b89bc4d3f 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/MemberStatisticsController.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/controller/admin/member/MemberStatisticsController.java @@ -39,7 +39,6 @@ public class MemberStatisticsController { @Resource private ApiAccessLogStatisticsService apiAccessLogStatisticsService; - // TODO 芋艿:已经 review @GetMapping("/summary") @Operation(summary = "获得会员统计(实时统计)") @PreAuthorize("@ss.hasPermission('statistics:member:query')") @@ -47,7 +46,6 @@ public class MemberStatisticsController { return success(memberStatisticsService.getMemberSummary()); } - // TODO 芋艿:已经 review @GetMapping("/analyse") @Operation(summary = "获得会员分析数据") @PreAuthorize("@ss.hasPermission('statistics:member:query')") @@ -76,7 +74,6 @@ public class MemberStatisticsController { return success(MemberStatisticsConvert.INSTANCE.convert(visitUserCount, orderUserCount, payUserCount, atv, comparisonData)); } - // TODO 芋艿:已经 review @GetMapping("/area-statistics-list") @Operation(summary = "按照省份,获得会员统计列表") @PreAuthorize("@ss.hasPermission('statistics:member:query')") @@ -84,7 +81,6 @@ public class MemberStatisticsController { return success(memberStatisticsService.getMemberAreaStatisticsList()); } - // TODO 芋艿:已经 review @GetMapping("/sex-statistics-list") @Operation(summary = "按照性别,获得会员统计列表") @PreAuthorize("@ss.hasPermission('statistics:member:query')") @@ -92,7 +88,6 @@ public class MemberStatisticsController { return success(memberStatisticsService.getMemberSexStatisticsList()); } - // TODO 芋艿:已经 review @GetMapping("/terminal-statistics-list") @Operation(summary = "按照终端,获得会员统计列表") @PreAuthorize("@ss.hasPermission('statistics:member:query')") @@ -100,7 +95,6 @@ public class MemberStatisticsController { return success(memberStatisticsService.getMemberTerminalStatisticsList()); } - // TODO 芋艿:已经 review // TODO @疯狂:要注意 date 的排序; @GetMapping("/user-count-comparison") @Operation(summary = "获得用户数量对照") @@ -109,7 +103,6 @@ public class MemberStatisticsController { return success(memberStatisticsService.getUserCountComparison()); } - // TODO 芋艿:已经 review @GetMapping("/register-count-list") @Operation(summary = "获得会员注册数量列表") @PreAuthorize("@ss.hasPermission('statistics:member:query')") diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/infra/ApiAccessLogStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/infra/ApiAccessLogStatisticsMapper.java index cf7f98a0e..5f76b58fd 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/infra/ApiAccessLogStatisticsMapper.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/infra/ApiAccessLogStatisticsMapper.java @@ -16,12 +16,10 @@ import java.time.LocalDateTime; @SuppressWarnings("rawtypes") public interface ApiAccessLogStatisticsMapper extends BaseMapperX { - // TODO 芋艿:已经 review Integer selectIpCountByUserTypeAndCreateTimeBetween(@Param("userType") Integer userType, @Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO 芋艿:已经 review Integer selectUserCountByUserTypeAndCreateTimeBetween(@Param("userType") Integer userType, @Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/member/MemberStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/member/MemberStatisticsMapper.java index 1d215b818..595e93d9e 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/member/MemberStatisticsMapper.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/member/MemberStatisticsMapper.java @@ -20,20 +20,15 @@ import java.util.List; @SuppressWarnings("rawtypes") public interface MemberStatisticsMapper extends BaseMapperX { - // TODO @芋艿:已经 review List selectSummaryListByAreaId(); - // TODO @芋艿:已经 review List selectSummaryListBySex(); - // TODO @芋艿:已经 review List selectSummaryListByRegisterTerminal(); - // TODO @芋艿:已经 review Integer selectUserCount(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO @芋艿:已经 review /** * 获得用户的每天注册数量列表 * diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/pay/PayWalletStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/pay/PayWalletStatisticsMapper.java index 90ab0e0c6..b9b38fbbe 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/pay/PayWalletStatisticsMapper.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/pay/PayWalletStatisticsMapper.java @@ -17,27 +17,22 @@ import java.time.LocalDateTime; @SuppressWarnings("rawtypes") public interface PayWalletStatisticsMapper extends BaseMapperX { - // TODO 芋艿:已经 review; WalletSummaryRespBO selectRechargeSummaryByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime, @Param("payStatus") Boolean payStatus); - // TODO 芋艿:已经 review; WalletSummaryRespBO selectRechargeSummaryByRefundTimeBetween(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime, @Param("refundStatus") Integer refundStatus); - // TODO 芋艿:已经 review; Integer selectPriceSummaryByBizTypeAndCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime, @Param("bizType") Integer bizType); - // TODO 芋艿:已经 review; RechargeSummaryRespBO selectRechargeSummaryGroupByWalletId(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime, @Param("payStatus") Boolean payStatus); - // TODO 芋艿:已经 review; Integer selectRechargePriceSummary(@Param("payStatus") Integer payStatus); } diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/AfterSaleStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/AfterSaleStatisticsMapper.java index 1d2bb8814..7de0cb07b 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/AfterSaleStatisticsMapper.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/AfterSaleStatisticsMapper.java @@ -16,11 +16,9 @@ import java.time.LocalDateTime; @Mapper public interface AfterSaleStatisticsMapper extends BaseMapperX { - // TODO 芋艿:已 review AfterSaleSummaryRespBO selectSummaryByRefundTimeBetween(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO 芋艿:已经 review Long selectCountByStatus(@Param("status") Integer status); } diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/BrokerageStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/BrokerageStatisticsMapper.java index 46d651a75..94ad06812 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/BrokerageStatisticsMapper.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/BrokerageStatisticsMapper.java @@ -15,13 +15,11 @@ import java.time.LocalDateTime; @Mapper public interface BrokerageStatisticsMapper extends BaseMapperX { - // TODO 芋艿:已经 review Integer selectSummaryPriceByStatusAndUnfreezeTimeBetween(@Param("bizType") Integer bizType, @Param("status") Integer status, @Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO 芋艿:已经 review Long selectWithdrawCountByStatus(@Param("status") Integer status); } diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeOrderStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeOrderStatisticsMapper.java index 43b4c4dfd..1d7ffb644 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeOrderStatisticsMapper.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeOrderStatisticsMapper.java @@ -19,30 +19,23 @@ import java.util.List; @Mapper public interface TradeOrderStatisticsMapper extends BaseMapperX { - // TODO 芋艿:已经 review List selectSummaryListByAreaId(); - // TODO 芋艿:已经 review Integer selectCountByCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO 芋艿:已经 review Integer selectCountByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO 芋艿:已经 review Integer selectSummaryPriceByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO 芋艿:已经 review Integer selectUserCountByCreateTimeBetween(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO 芋艿:已经 review Integer selectUserCountByPayTimeBetween(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 按照支付时间统计订单(按天分组) * @@ -53,7 +46,6 @@ public interface TradeOrderStatisticsMapper extends BaseMapperX selectListByPayTimeBetweenAndGroupByDay(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 按照支付时间统计订单(按月分组) * @@ -64,10 +56,8 @@ public interface TradeOrderStatisticsMapper extends BaseMapperX selectListByPayTimeBetweenAndGroupByMonth(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO @芋艿:已经 review Long selectCountByStatusAndDeliveryType(@Param("status") Integer status, @Param("deliveryType") Integer deliveryType); - // TODO 芋艿:已经 review TradeOrderSummaryRespVO selectPaySummaryByStatusAndPayTimeBetween(@Param("status") Integer status, @Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeStatisticsMapper.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeStatisticsMapper.java index d5ed54cec..67d2d5007 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeStatisticsMapper.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/dal/mysql/trade/TradeStatisticsMapper.java @@ -25,17 +25,14 @@ public interface TradeStatisticsMapper extends BaseMapperX { TradeTrendSummaryRespVO selectVoByTimeBetween(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO @芋艿:已经 review default List selectListByTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) { return selectList(new LambdaQueryWrapperX() .between(TradeStatisticsDO::getTime, beginTime, endTime)); } - // TODO @芋艿:已经 review Integer selectExpensePriceByTimeBetween(@Param("beginTime") LocalDateTime beginTime, @Param("endTime") LocalDateTime endTime); - // TODO @芋艿:已经 review default TradeStatisticsDO selectByTimeBetween(LocalDateTime beginTime, LocalDateTime endTime) { return selectOne(new LambdaQueryWrapperX() .between(TradeStatisticsDO::getTime, beginTime, endTime)); diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsService.java index f4042730b..6c200fdef 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsService.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/infra/ApiAccessLogStatisticsService.java @@ -9,7 +9,6 @@ import java.time.LocalDateTime; */ public interface ApiAccessLogStatisticsService { - // TODO 芋艿:已经 review /** * 获取活跃用户数量 * @@ -20,7 +19,6 @@ public interface ApiAccessLogStatisticsService { */ Integer getUserCount(Integer userType, LocalDateTime beginTime, LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 获取访问用户数量 * diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsService.java index 324905e59..253cba68a 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsService.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/member/MemberStatisticsService.java @@ -13,7 +13,6 @@ import java.util.List; */ public interface MemberStatisticsService { - // TODO 芋艿:已经 review /** * 获取会员统计(实时统计) * @@ -21,7 +20,6 @@ public interface MemberStatisticsService { */ MemberSummaryRespVO getMemberSummary(); - // TODO 芋艿:已经 review /** * 获取会员分析对照数据 * @@ -32,7 +30,6 @@ public interface MemberStatisticsService { DataComparisonRespVO getMemberAnalyseComparisonData(LocalDateTime beginTime, LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 按照省份,获得会员统计列表 * @@ -40,7 +37,6 @@ public interface MemberStatisticsService { */ List getMemberAreaStatisticsList(); - // TODO 芋艿:已经 review /** * 按照性别,获得会员统计列表 * @@ -55,7 +51,6 @@ public interface MemberStatisticsService { */ List getMemberTerminalStatisticsList(); - // TODO 芋艿:已经 review /** * 获取用户注册数量列表 * @@ -65,7 +60,6 @@ public interface MemberStatisticsService { */ List getMemberRegisterCountList(LocalDateTime beginTime, LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 获得用户数量量统计对照 * diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsService.java index 4801ad186..d43a57682 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsService.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/pay/PayWalletStatisticsService.java @@ -12,7 +12,6 @@ import java.time.LocalDateTime; */ public interface PayWalletStatisticsService { - // TODO 芋艿:已经 review /** * 获取钱包统计 * @@ -22,7 +21,6 @@ public interface PayWalletStatisticsService { */ WalletSummaryRespBO getWalletSummary(LocalDateTime beginTime, LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 获取钱包充值统计 * @@ -32,7 +30,6 @@ public interface PayWalletStatisticsService { */ RechargeSummaryRespBO getUserRechargeSummary(LocalDateTime beginTime, LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 获取充值金额合计 * diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsService.java index 3904387d6..f584c3dc7 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsService.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/AfterSaleStatisticsService.java @@ -12,7 +12,6 @@ import java.time.LocalDateTime; */ public interface AfterSaleStatisticsService { - // TODO 芋艿:已经 review /** * 获取售后单统计 * @@ -22,7 +21,6 @@ public interface AfterSaleStatisticsService { */ AfterSaleSummaryRespBO getAfterSaleSummary(LocalDateTime beginTime, LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 获取指定状态的售后订单数量 * diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsService.java index 5f856778d..6845b608a 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsService.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/BrokerageStatisticsService.java @@ -11,7 +11,6 @@ import java.time.LocalDateTime; */ public interface BrokerageStatisticsService { - // TODO 芋艿:已经 review /** * 获取已结算的佣金金额 * @@ -21,7 +20,6 @@ public interface BrokerageStatisticsService { */ Integer getBrokerageSettlementPriceSummary(LocalDateTime beginTime, LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 获取指定状态的提现记录数量 * diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsService.java index 982957e24..2197515c4 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsService.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeOrderStatisticsService.java @@ -24,7 +24,6 @@ public interface TradeOrderStatisticsService { */ TradeOrderSummaryRespBO getOrderSummary(LocalDateTime beginTime, LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 获取地区订单统计 * @@ -32,7 +31,6 @@ public interface TradeOrderStatisticsService { */ List getSummaryListByAreaId(); - // TODO 芋艿:已经 review /** * 获取下单用户数量 * @@ -42,7 +40,6 @@ public interface TradeOrderStatisticsService { */ Integer getOrderUserCount(LocalDateTime beginTime, LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 获取支付用户数量 * @@ -52,7 +49,6 @@ public interface TradeOrderStatisticsService { */ Integer getPayUserCount(LocalDateTime beginTime, LocalDateTime endTime); - // TODO 芋艿:已经 review /** * 获取支付金额 * @@ -69,7 +65,6 @@ public interface TradeOrderStatisticsService { */ Long getCountByStatusAndDeliveryType(Integer status, Integer deliveryType); - // TODO 芋艿:已经 review /** * 交易订单销售额对照 * @@ -77,7 +72,6 @@ public interface TradeOrderStatisticsService { */ DataComparisonRespVO getOrderComparison(); - // TODO 芋艿:已经 review /** * 获得订单量趋势统计 * diff --git a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsService.java b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsService.java index 225bccf9f..e175985fb 100644 --- a/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsService.java +++ b/yudao-module-mall/yudao-module-statistics-biz/src/main/java/cn/iocoder/yudao/module/statistics/service/trade/TradeStatisticsService.java @@ -41,7 +41,6 @@ public interface TradeStatisticsService { */ List getTradeStatisticsList(LocalDateTime beginTime, LocalDateTime endTime); - // TODO 芋艿:已经 review; /** * 统计指定天数的交易数据 * @@ -49,7 +48,6 @@ public interface TradeStatisticsService { */ String statisticsTrade(Integer days); - // TODO 芋艿:已经 review /** * 统计指定日期的交易数据 * @@ -58,7 +56,6 @@ public interface TradeStatisticsService { */ TradeSummaryRespBO getTradeSummaryByDays(int days); - // TODO 芋艿:已经 review /** * 统计指定月份的交易数据 * diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java index 107184564..5c47f4de3 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApi.java @@ -3,6 +3,9 @@ package cn.iocoder.yudao.module.system.api.dict; import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; import java.util.Collection; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; /** * 字典数据 API 接口 @@ -39,4 +42,23 @@ public interface DictDataApi { */ DictDataRespDTO parseDictData(String type, String label); + /** + * 获得指定字典类型的字典数据列表 + * + * @param dictType 字典类型 + * @return 字典数据列表 + */ + List getDictDataList(String dictType); + + /** + * 获得字典数据标签列表 + * + * @param dictType 字典类型 + * @return 字典数据标签列表 + */ + default List getDictDataLabelList(String dictType) { + List list = getDictDataList(dictType); + return convertList(list, DictDataRespDTO::getLabel); + } + } diff --git a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/permission/RoleCodeEnum.java b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/permission/RoleCodeEnum.java index b13b3b71a..cdf9a001d 100644 --- a/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/permission/RoleCodeEnum.java +++ b/yudao-module-system/yudao-module-system-api/src/main/java/cn/iocoder/yudao/module/system/enums/permission/RoleCodeEnum.java @@ -13,6 +13,7 @@ public enum RoleCodeEnum { SUPER_ADMIN("super_admin", "超级管理员"), TENANT_ADMIN("tenant_admin", "租户管理员"), + CRM_ADMIN("crm_admin", "CRM 管理员"); // CRM 系统专用 ; /** diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApiImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApiImpl.java index 55313c7cf..450512784 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApiImpl.java +++ b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/api/dict/DictDataApiImpl.java @@ -4,10 +4,11 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO; import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO; import cn.iocoder.yudao.module.system.service.dict.DictDataService; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; -import jakarta.annotation.Resource; import java.util.Collection; +import java.util.List; /** * 字典数据 API 实现类 @@ -37,4 +38,10 @@ public class DictDataApiImpl implements DictDataApi { return BeanUtils.toBean(dictData, DictDataRespDTO.class); } + @Override + public List getDictDataList(String dictType) { + List list = dictDataService.getDictDataListByDictType(dictType); + return BeanUtils.toBean(list, DictDataRespDTO.class); + } + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/ip/AreaController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/ip/AreaController.java index b2f95d69f..b39e0a06d 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/ip/AreaController.java +++ b/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 { @GetMapping("/tree") @Operation(summary = "获得地区树") public CommonResult> getAreaTree() { - Area area = AreaUtils.getArea(Area.ID_CHINA); + Area area = AreaUtils.parseArea(Area.ID_CHINA); Assert.notNull(area, "获取不到中国"); return success(BeanUtils.toBean(area.getChildren(), AreaNodeRespVO.class)); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/ip/AppAreaController.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/ip/AppAreaController.java index 54b0e87db..759b72c72 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/app/ip/AppAreaController.java +++ b/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 { @GetMapping("/tree") @Operation(summary = "获得地区树") public CommonResult> getAreaTree() { - Area area = AreaUtils.getArea(Area.ID_CHINA); + Area area = AreaUtils.parseArea(Area.ID_CHINA); Assert.notNull(area, "获取不到中国"); return success(BeanUtils.toBean(area.getChildren(), AppAreaNodeRespVO.class)); } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java index a752476da..fc2498f1b 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataService.java +++ b/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 { */ DictDataDO parseDictData(String dictType, String label); + /** + * 获得指定数据类型的字典数据列表 + * + * @param dictType 字典类型 + * @return 字典数据列表 + */ + List getDictDataListByDictType(String dictType); + } diff --git a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java b/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java index 95ef27ef5..b15fd506a 100644 --- a/yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/dict/DictDataServiceImpl.java +++ b/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; import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO; import cn.iocoder.yudao.module.system.dal.mysql.dict.DictDataMapper; import com.google.common.annotations.VisibleForTesting; +import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import jakarta.annotation.Resource; import java.util.Collection; import java.util.Comparator; import java.util.List; @@ -169,4 +169,11 @@ public class DictDataServiceImpl implements DictDataService { return dictDataMapper.selectByDictTypeAndLabel(dictType, label); } + @Override + public List getDictDataListByDictType(String dictType) { + List list = dictDataMapper.selectList(DictDataDO::getDictType, dictType); + list.sort(Comparator.comparing(DictDataDO::getSort)); + return list; + } + } diff --git a/yudao-server/pom.xml b/yudao-server/pom.xml index bc850b590..ad4382e00 100644 --- a/yudao-server/pom.xml +++ b/yudao-server/pom.xml @@ -88,11 +88,11 @@ - - - - - + + cn.iocoder.boot + yudao-module-crm-biz + ${revision} +