Commit 4605e931051898ef9a5d3fbe948f8458638ab099

Authored by puhui999
1 parent d8b2da8e

CRM-客户: 完善客户导入

yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
@@ -26,8 +26,8 @@ public interface ErrorCodeConstants { @@ -26,8 +26,8 @@ public interface ErrorCodeConstants {
26 26
27 // ========== 联系人管理 1-020-003-000 ========== 27 // ========== 联系人管理 1-020-003-000 ==========
28 ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在"); 28 ErrorCode CONTACT_NOT_EXISTS = new ErrorCode(1_020_003_000, "联系人不存在");
29 - ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode( 1_020_003_001, "联系人商机关联不存在");  
30 - ErrorCode CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS = new ErrorCode( 1_020_003_002, "联系人已关联合同,不能删除"); 29 + ErrorCode CONTACT_BUSINESS_LINK_NOT_EXISTS = new ErrorCode(1_020_003_001, "联系人商机关联不存在");
  30 + ErrorCode CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS = new ErrorCode(1_020_003_002, "联系人已关联合同,不能删除");
31 31
32 // ========== 回款 1-020-004-000 ========== 32 // ========== 回款 1-020-004-000 ==========
33 ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在"); 33 ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在");
@@ -48,6 +48,9 @@ public interface ErrorCodeConstants { @@ -48,6 +48,9 @@ public interface ErrorCodeConstants {
48 ErrorCode CUSTOMER_LOCK_EXCEED_LIMIT = new ErrorCode(1_020_006_009, "锁定客户失败,超出锁定规则上限"); 48 ErrorCode CUSTOMER_LOCK_EXCEED_LIMIT = new ErrorCode(1_020_006_009, "锁定客户失败,超出锁定规则上限");
49 ErrorCode CUSTOMER_OWNER_EXCEED_LIMIT = new ErrorCode(1_020_006_010, "操作失败,超出客户数拥有上限"); 49 ErrorCode CUSTOMER_OWNER_EXCEED_LIMIT = new ErrorCode(1_020_006_010, "操作失败,超出客户数拥有上限");
50 ErrorCode CUSTOMER_DELETE_FAIL_HAVE_REFERENCE = new ErrorCode(1_020_006_011, "删除客户失败,有关联{}"); 50 ErrorCode CUSTOMER_DELETE_FAIL_HAVE_REFERENCE = new ErrorCode(1_020_006_011, "删除客户失败,有关联{}");
  51 + ErrorCode CUSTOMER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_020_006_012, "导入客户数据不能为空!");
  52 + ErrorCode CUSTOMER_CREATE_NAME_NOT_NULL = new ErrorCode(1_020_006_013, "客户名称不能为空!");
  53 + ErrorCode CUSTOMER_NAME_EXISTS = new ErrorCode(1_020_006_014, "已存在名为【{}】的客户!");
51 54
52 // ========== 权限管理 1_020_007_000 ========== 55 // ========== 权限管理 1_020_007_000 ==========
53 ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在"); 56 ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
@@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi; @@ -21,6 +21,7 @@ import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
21 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; 21 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
22 import io.swagger.v3.oas.annotations.Operation; 22 import io.swagger.v3.oas.annotations.Operation;
23 import io.swagger.v3.oas.annotations.Parameter; 23 import io.swagger.v3.oas.annotations.Parameter;
  24 +import io.swagger.v3.oas.annotations.Parameters;
24 import io.swagger.v3.oas.annotations.tags.Tag; 25 import io.swagger.v3.oas.annotations.tags.Tag;
25 import jakarta.annotation.Resource; 26 import jakarta.annotation.Resource;
26 import jakarta.servlet.http.HttpServletResponse; 27 import jakarta.servlet.http.HttpServletResponse;
@@ -29,9 +30,11 @@ import org.mapstruct.ap.internal.util.Collections; @@ -29,9 +30,11 @@ import org.mapstruct.ap.internal.util.Collections;
29 import org.springframework.security.access.prepost.PreAuthorize; 30 import org.springframework.security.access.prepost.PreAuthorize;
30 import org.springframework.validation.annotation.Validated; 31 import org.springframework.validation.annotation.Validated;
31 import org.springframework.web.bind.annotation.*; 32 import org.springframework.web.bind.annotation.*;
  33 +import org.springframework.web.multipart.MultipartFile;
32 34
33 import java.io.IOException; 35 import java.io.IOException;
34 import java.time.LocalDateTime; 36 import java.time.LocalDateTime;
  37 +import java.util.Arrays;
35 import java.util.List; 38 import java.util.List;
36 import java.util.Map; 39 import java.util.Map;
37 import java.util.stream.Stream; 40 import java.util.stream.Stream;
@@ -169,6 +172,36 @@ public class CrmCustomerController { @@ -169,6 +172,36 @@ public class CrmCustomerController {
169 BeanUtils.toBean(list, CrmCustomerRespVO.class)); 172 BeanUtils.toBean(list, CrmCustomerRespVO.class));
170 } 173 }
171 174
  175 + @GetMapping("/get-import-template")
  176 + @Operation(summary = "获得导入客户模板")
  177 + public void importTemplate(HttpServletResponse response) throws IOException {
  178 + // 手动创建导出 demo
  179 + List<CrmCustomerImportExcelVO> list = Arrays.asList(
  180 + CrmCustomerImportExcelVO.builder().name("芋道").industryId(1).level(1).source(1).mobile("15601691300").telephone("")
  181 + .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("")
  182 + .areaId(null).detailAddress("").build(),
  183 + CrmCustomerImportExcelVO.builder().name("源码").industryId(1).level(1).source(1).mobile("15601691300").telephone("")
  184 + .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("")
  185 + .areaId(null).detailAddress("").build()
  186 + );
  187 + // 输出
  188 + ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list);
  189 + }
  190 +
  191 + @PostMapping("/import")
  192 + @Operation(summary = "导入客户")
  193 + @Parameters({
  194 + @Parameter(name = "file", description = "Excel 文件", required = true),
  195 + @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
  196 + })
  197 + @PreAuthorize("@ss.hasPermission('system:customer:import')")
  198 + public CommonResult<CrmCustomerImportRespVO> importExcel(@RequestParam("file") MultipartFile file,
  199 + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
  200 + List<CrmCustomerImportExcelVO> list = ExcelUtils.read(file, CrmCustomerImportExcelVO.class);
  201 + return success(customerService.importCustomerList(list, updateSupport));
  202 + }
  203 +
  204 +
172 @PutMapping("/transfer") 205 @PutMapping("/transfer")
173 @Operation(summary = "转移客户") 206 @Operation(summary = "转移客户")
174 @PreAuthorize("@ss.hasPermission('crm:customer:update')") 207 @PreAuthorize("@ss.hasPermission('crm:customer:update')")
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
  4 +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
  5 +import com.alibaba.excel.annotation.ExcelProperty;
  6 +import jakarta.validation.constraints.Size;
  7 +import lombok.AllArgsConstructor;
  8 +import lombok.Builder;
  9 +import lombok.Data;
  10 +import lombok.NoArgsConstructor;
  11 +import lombok.experimental.Accessors;
  12 +
  13 +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*;
  14 +
  15 +/**
  16 + * 客户 Excel 导入 VO
  17 + */
  18 +@Data
  19 +@Builder
  20 +@AllArgsConstructor
  21 +@NoArgsConstructor
  22 +@Accessors(chain = false) // 设置 chain = false,避免用户导入有问题
  23 +public class CrmCustomerImportExcelVO {
  24 +
  25 + @ExcelProperty("客户名称")
  26 + private String name;
  27 +
  28 + @ExcelProperty(value = "所属行业", converter = DictConvert.class)
  29 + @DictFormat(CRM_CUSTOMER_INDUSTRY)
  30 + private Integer industryId;
  31 +
  32 + @ExcelProperty(value = "客户等级", converter = DictConvert.class)
  33 + @DictFormat(CRM_CUSTOMER_LEVEL)
  34 + private Integer level;
  35 +
  36 + @ExcelProperty(value = "客户来源", converter = DictConvert.class)
  37 + @DictFormat(CRM_CUSTOMER_SOURCE)
  38 + private Integer source;
  39 +
  40 + @ExcelProperty("手机")
  41 + private String mobile;
  42 +
  43 + @ExcelProperty("电话")
  44 + private String telephone;
  45 +
  46 + @ExcelProperty("网址")
  47 + private String website;
  48 +
  49 + @Size(max = 20, message = "QQ长度不能超过 20 个字符")
  50 + @ExcelProperty("QQ")
  51 + private String qq;
  52 +
  53 + @Size(max = 255, message = "微信长度不能超过 255 个字符")
  54 + @ExcelProperty("微信")
  55 + private String wechat;
  56 +
  57 + @Size(max = 255, message = "邮箱长度不能超过 255 个字符")
  58 + @ExcelProperty("邮箱")
  59 + private String email;
  60 +
  61 + @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符")
  62 + @ExcelProperty("客户描述")
  63 + private String description;
  64 +
  65 + @ExcelProperty("备注")
  66 + private String remark;
  67 +
  68 + @ExcelProperty("地区编号")
  69 + private Integer areaId;
  70 +
  71 + @ExcelProperty("详细地址")
  72 + private String detailAddress;
  73 +
  74 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Builder;
  5 +import lombok.Data;
  6 +
  7 +import java.util.List;
  8 +import java.util.Map;
  9 +
  10 +@Schema(description = "管理后台 - 客户导入 Response VO")
  11 +@Data
  12 +@Builder
  13 +public class CrmCustomerImportRespVO {
  14 +
  15 + @Schema(description = "创建成功的客户名数组", requiredMode = Schema.RequiredMode.REQUIRED)
  16 + private List<String> createCustomerNames;
  17 +
  18 + @Schema(description = "更新成功的客户名数组", requiredMode = Schema.RequiredMode.REQUIRED)
  19 + private List<String> updateCustomerNames;
  20 +
  21 + @Schema(description = "导入失败的客户集合,key 为客户名,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
  22 + private Map<String, String> failureCustomerNames;
  23 +
  24 +}
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
@@ -107,4 +107,8 @@ public interface CrmCustomerMapper extends BaseMapperX&lt;CrmCustomerDO&gt; { @@ -107,4 +107,8 @@ public interface CrmCustomerMapper extends BaseMapperX&lt;CrmCustomerDO&gt; {
107 .gt(CrmCustomerDO::getOwnerUserId, 0)); 107 .gt(CrmCustomerDO::getOwnerUserId, 0));
108 } 108 }
109 109
  110 + default CrmCustomerDO selectByCustomerName(String name) {
  111 + return selectOne(CrmCustomerDO::getName, name);
  112 + }
  113 +
110 } 114 }
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
1 package cn.iocoder.yudao.module.crm.service.customer; 1 package cn.iocoder.yudao.module.crm.service.customer;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLockReqVO;  
5 -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;  
6 -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO;  
7 -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO; 4 +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
8 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 5 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
9 import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; 6 import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
10 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; 7 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
@@ -108,6 +105,15 @@ public interface CrmCustomerService { @@ -108,6 +105,15 @@ public interface CrmCustomerService {
108 */ 105 */
109 Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId); 106 Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId);
110 107
  108 + /**
  109 + * 批量导入客户
  110 + *
  111 + * @param importCustomers 导入客户列表
  112 + * @param isUpdateSupport 是否支持更新
  113 + * @return 导入结果
  114 + */
  115 + CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers, Boolean isUpdateSupport);
  116 +
111 // ==================== 公海相关操作 ==================== 117 // ==================== 公海相关操作 ====================
112 118
113 /** 119 /**
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
@@ -3,15 +3,14 @@ package cn.iocoder.yudao.module.crm.service.customer; @@ -3,15 +3,14 @@ package cn.iocoder.yudao.module.crm.service.customer;
3 import cn.hutool.core.collection.CollUtil; 3 import cn.hutool.core.collection.CollUtil;
4 import cn.hutool.core.lang.Assert; 4 import cn.hutool.core.lang.Assert;
5 import cn.hutool.core.util.ObjUtil; 5 import cn.hutool.core.util.ObjUtil;
  6 +import cn.hutool.core.util.StrUtil;
6 import cn.hutool.extra.spring.SpringUtil; 7 import cn.hutool.extra.spring.SpringUtil;
  8 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
7 import cn.iocoder.yudao.framework.common.pojo.PageResult; 9 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 10 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
9 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; 11 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
11 -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerLockReqVO;  
12 -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO;  
13 -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO;  
14 -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO; 13 +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*;
15 import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert; 14 import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; 15 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
17 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
@@ -41,10 +40,7 @@ import org.springframework.transaction.annotation.Transactional; @@ -41,10 +40,7 @@ import org.springframework.transaction.annotation.Transactional;
41 import org.springframework.validation.annotation.Validated; 40 import org.springframework.validation.annotation.Validated;
42 41
43 import java.time.LocalDateTime; 42 import java.time.LocalDateTime;
44 -import java.util.ArrayList;  
45 -import java.util.Collection;  
46 -import java.util.Collections;  
47 -import java.util.List; 43 +import java.util.*;
48 44
49 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 45 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
50 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; 46 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
@@ -236,7 +232,50 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { @@ -236,7 +232,50 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
236 return customer.getId(); 232 return customer.getId();
237 } 233 }
238 234
239 -// ==================== 公海相关操作 ==================== 235 + @Override
  236 + public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers, Boolean isUpdateSupport) {
  237 + if (CollUtil.isEmpty(importCustomers)) {
  238 + throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY);
  239 + }
  240 + CrmCustomerImportRespVO respVO = CrmCustomerImportRespVO.builder().createCustomerNames(new ArrayList<>())
  241 + .updateCustomerNames(new ArrayList<>()).failureCustomerNames(new LinkedHashMap<>()).build();
  242 + importCustomers.forEach(importCustomer -> {
  243 + // 校验,判断是否有不符合的原因
  244 + try {
  245 + validateCustomerForCreate(importCustomer);
  246 + } catch (ServiceException ex) {
  247 + respVO.getFailureCustomerNames().put(importCustomer.getName(), ex.getMessage());
  248 + return;
  249 + }
  250 + // 判断如果不存在,在进行插入
  251 + CrmCustomerDO existCustomer = customerMapper.selectByCustomerName(importCustomer.getName());
  252 + if (existCustomer == null) {
  253 + customerMapper.insert(BeanUtils.toBean(importCustomer, CrmCustomerDO.class));
  254 + respVO.getCreateCustomerNames().add(importCustomer.getName());
  255 + return;
  256 + }
  257 + // 如果存在,判断是否允许更新
  258 + if (!isUpdateSupport) {
  259 + respVO.getFailureCustomerNames().put(importCustomer.getName(),
  260 + StrUtil.format(CUSTOMER_NAME_EXISTS.getMsg(), importCustomer.getName()));
  261 + return;
  262 + }
  263 + CrmCustomerDO updateCustomer = BeanUtils.toBean(importCustomer, CrmCustomerDO.class);
  264 + updateCustomer.setId(existCustomer.getId());
  265 + customerMapper.updateById(updateCustomer);
  266 + respVO.getUpdateCustomerNames().add(importCustomer.getName());
  267 + });
  268 + return respVO;
  269 + }
  270 +
  271 + private void validateCustomerForCreate(CrmCustomerImportExcelVO importCustomer) {
  272 + // 校验客户名称不能为空
  273 + if (StrUtil.isEmptyIfStr(importCustomer.getName())) {
  274 + throw exception(CUSTOMER_CREATE_NAME_NOT_NULL);
  275 + }
  276 + }
  277 +
  278 + // ==================== 公海相关操作 ====================
240 279
241 @Override 280 @Override
242 @Transactional(rollbackFor = Exception.class) 281 @Transactional(rollbackFor = Exception.class)