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 26  
27 27 // ========== 联系人管理 1-020-003-000 ==========
28 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 32 // ========== 回款 1-020-004-000 ==========
33 33 ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在");
... ... @@ -48,6 +48,9 @@ public interface ErrorCodeConstants {
48 48 ErrorCode CUSTOMER_LOCK_EXCEED_LIMIT = new ErrorCode(1_020_006_009, "锁定客户失败,超出锁定规则上限");
49 49 ErrorCode CUSTOMER_OWNER_EXCEED_LIMIT = new ErrorCode(1_020_006_010, "操作失败,超出客户数拥有上限");
50 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 55 // ========== 权限管理 1_020_007_000 ==========
53 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 21 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
22 22 import io.swagger.v3.oas.annotations.Operation;
23 23 import io.swagger.v3.oas.annotations.Parameter;
  24 +import io.swagger.v3.oas.annotations.Parameters;
24 25 import io.swagger.v3.oas.annotations.tags.Tag;
25 26 import jakarta.annotation.Resource;
26 27 import jakarta.servlet.http.HttpServletResponse;
... ... @@ -29,9 +30,11 @@ import org.mapstruct.ap.internal.util.Collections;
29 30 import org.springframework.security.access.prepost.PreAuthorize;
30 31 import org.springframework.validation.annotation.Validated;
31 32 import org.springframework.web.bind.annotation.*;
  33 +import org.springframework.web.multipart.MultipartFile;
32 34  
33 35 import java.io.IOException;
34 36 import java.time.LocalDateTime;
  37 +import java.util.Arrays;
35 38 import java.util.List;
36 39 import java.util.Map;
37 40 import java.util.stream.Stream;
... ... @@ -169,6 +172,36 @@ public class CrmCustomerController {
169 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 205 @PutMapping("/transfer")
173 206 @Operation(summary = "转移客户")
174 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 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 1 package cn.iocoder.yudao.module.crm.service.customer;
2 2  
3 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 5 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
9 6 import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
10 7 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
... ... @@ -108,6 +105,15 @@ public interface CrmCustomerService {
108 105 */
109 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 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.lang.Assert;
5 5 import cn.hutool.core.util.ObjUtil;
  6 +import cn.hutool.core.util.StrUtil;
6 7 import cn.hutool.extra.spring.SpringUtil;
  8 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
7 9 import cn.iocoder.yudao.framework.common.pojo.PageResult;
8 10 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
9 11 import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
10 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 14 import cn.iocoder.yudao.module.crm.convert.customer.CrmCustomerConvert;
16 15 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO;
17 16 import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO;
... ... @@ -41,10 +40,7 @@ import org.springframework.transaction.annotation.Transactional;
41 40 import org.springframework.validation.annotation.Validated;
42 41  
43 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 45 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
50 46 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
... ... @@ -236,7 +232,50 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
236 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 280 @Override
242 281 @Transactional(rollbackFor = Exception.class)
... ...