From 4605e931051898ef9a5d3fbe948f8458638ab099 Mon Sep 17 00:00:00 2001 From: puhui999 Date: Sat, 27 Jan 2024 14:30:30 +0800 Subject: [PATCH] =?UTF-8?q?CRM-=E5=AE=A2=E6=88=B7:=20=E5=AE=8C=E5=96=84?= =?UTF-8?q?=E5=AE=A2=E6=88=B7=E5=AF=BC=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/crm/enums/ErrorCodeConstants.java | 7 +- .../admin/customer/CrmCustomerController.java | 33 +++++++++ .../customer/vo/CrmCustomerImportExcelVO.java | 74 +++++++++++++++++++ .../customer/vo/CrmCustomerImportRespVO.java | 24 ++++++ .../dal/mysql/customer/CrmCustomerMapper.java | 4 + .../service/customer/CrmCustomerService.java | 14 +++- .../customer/CrmCustomerServiceImpl.java | 57 +++++++++++--- 7 files changed, 198 insertions(+), 15 deletions(-) create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java 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 737e4100d..9db937aa6 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 @@ -26,8 +26,8 @@ public interface ErrorCodeConstants { // ========== 联系人管理 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_BUSINESS_LINK_NOT_EXISTS = new ErrorCode(1_020_003_001, "联系人商机关联不存在"); + ErrorCode CONTACT_DELETE_FAIL_CONTRACT_LINK_EXISTS = new ErrorCode(1_020_003_002, "联系人已关联合同,不能删除"); // ========== 回款 1-020-004-000 ========== ErrorCode RECEIVABLE_NOT_EXISTS = new ErrorCode(1_020_004_000, "回款不存在"); @@ -48,6 +48,9 @@ public interface ErrorCodeConstants { ErrorCode CUSTOMER_LOCK_EXCEED_LIMIT = new ErrorCode(1_020_006_009, "锁定客户失败,超出锁定规则上限"); ErrorCode CUSTOMER_OWNER_EXCEED_LIMIT = new ErrorCode(1_020_006_010, "操作失败,超出客户数拥有上限"); ErrorCode CUSTOMER_DELETE_FAIL_HAVE_REFERENCE = new ErrorCode(1_020_006_011, "删除客户失败,有关联{}"); + 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, "已存在名为【{}】的客户!"); // ========== 权限管理 1_020_007_000 ========== ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在"); 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 03893b817..fe802f86d 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 @@ -21,6 +21,7 @@ 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; @@ -29,9 +30,11 @@ 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 org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.time.LocalDateTime; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -169,6 +172,36 @@ public class CrmCustomerController { BeanUtils.toBean(list, CrmCustomerRespVO.class)); } + @GetMapping("/get-import-template") + @Operation(summary = "获得导入客户模板") + 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() + ); + // 输出 + ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list); + } + + @PostMapping("/import") + @Operation(summary = "导入客户") + @Parameters({ + @Parameter(name = "file", description = "Excel 文件", required = true), + @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true") + }) + @PreAuthorize("@ss.hasPermission('system:customer:import')") + public CommonResult importExcel(@RequestParam("file") MultipartFile file, + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { + List list = ExcelUtils.read(file, CrmCustomerImportExcelVO.class); + return success(customerService.importCustomerList(list, updateSupport)); + } + + @PutMapping("/transfer") @Operation(summary = "转移客户") @PreAuthorize("@ss.hasPermission('crm:customer:update')") 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/CrmCustomerImportExcelVO.java new file mode 100644 index 000000000..378abb6dd --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportExcelVO.java @@ -0,0 +1,74 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat; +import cn.iocoder.yudao.framework.excel.core.convert.DictConvert; +import com.alibaba.excel.annotation.ExcelProperty; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import static cn.iocoder.yudao.module.crm.enums.DictTypeConstants.*; + +/** + * 客户 Excel 导入 VO + */ +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@Accessors(chain = false) // 设置 chain = false,避免用户导入有问题 +public class CrmCustomerImportExcelVO { + + @ExcelProperty("客户名称") + private String name; + + @ExcelProperty(value = "所属行业", converter = DictConvert.class) + @DictFormat(CRM_CUSTOMER_INDUSTRY) + private Integer industryId; + + @ExcelProperty(value = "客户等级", converter = DictConvert.class) + @DictFormat(CRM_CUSTOMER_LEVEL) + private Integer level; + + @ExcelProperty(value = "客户来源", converter = DictConvert.class) + @DictFormat(CRM_CUSTOMER_SOURCE) + private Integer source; + + @ExcelProperty("手机") + private String mobile; + + @ExcelProperty("电话") + private String telephone; + + @ExcelProperty("网址") + private String website; + + @Size(max = 20, message = "QQ长度不能超过 20 个字符") + @ExcelProperty("QQ") + private String qq; + + @Size(max = 255, message = "微信长度不能超过 255 个字符") + @ExcelProperty("微信") + private String wechat; + + @Size(max = 255, message = "邮箱长度不能超过 255 个字符") + @ExcelProperty("邮箱") + private String email; + + @Size(max = 4096, message = "客户描述长度不能超过 4096 个字符") + @ExcelProperty("客户描述") + private String description; + + @ExcelProperty("备注") + private String remark; + + @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/CrmCustomerImportRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java new file mode 100644 index 000000000..de35b7b92 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/vo/CrmCustomerImportRespVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.crm.controller.admin.customer.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - 客户导入 Response VO") +@Data +@Builder +public class CrmCustomerImportRespVO { + + @Schema(description = "创建成功的客户名数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List createCustomerNames; + + @Schema(description = "更新成功的客户名数组", requiredMode = Schema.RequiredMode.REQUIRED) + private List updateCustomerNames; + + @Schema(description = "导入失败的客户集合,key 为客户名,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED) + private Map failureCustomerNames; + +} 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 f0c18f52d..af8b0d176 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 @@ -107,4 +107,8 @@ public interface CrmCustomerMapper extends BaseMapperX { .gt(CrmCustomerDO::getOwnerUserId, 0)); } + default CrmCustomerDO selectByCustomerName(String name) { + return selectOne(CrmCustomerDO::getName, name); + } + } 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 dc5c9448e..76e91b7d4 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,10 +1,7 @@ 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.CrmCustomerLockReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.*; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; @@ -108,6 +105,15 @@ public interface CrmCustomerService { */ Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId); + /** + * 批量导入客户 + * + * @param importCustomers 导入客户列表 + * @param isUpdateSupport 是否支持更新 + * @return 导入结果 + */ + CrmCustomerImportRespVO importCustomerList(List importCustomers, Boolean isUpdateSupport); + // ==================== 公海相关操作 ==================== /** 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 ace7b21d4..78493e046 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 @@ -3,15 +3,14 @@ package cn.iocoder.yudao.module.crm.service.customer; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; 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.CrmCustomerLockReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; -import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTransferReqVO; +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.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfigDO; @@ -41,10 +40,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; @@ -236,7 +232,50 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { return customer.getId(); } -// ==================== 公海相关操作 ==================== + @Override + public CrmCustomerImportRespVO importCustomerList(List importCustomers, Boolean isUpdateSupport) { + 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 -> { + // 校验,判断是否有不符合的原因 + try { + validateCustomerForCreate(importCustomer); + } catch (ServiceException ex) { + respVO.getFailureCustomerNames().put(importCustomer.getName(), ex.getMessage()); + return; + } + // 判断如果不存在,在进行插入 + CrmCustomerDO existCustomer = customerMapper.selectByCustomerName(importCustomer.getName()); + if (existCustomer == null) { + customerMapper.insert(BeanUtils.toBean(importCustomer, CrmCustomerDO.class)); + respVO.getCreateCustomerNames().add(importCustomer.getName()); + return; + } + // 如果存在,判断是否允许更新 + if (!isUpdateSupport) { + respVO.getFailureCustomerNames().put(importCustomer.getName(), + StrUtil.format(CUSTOMER_NAME_EXISTS.getMsg(), importCustomer.getName())); + return; + } + CrmCustomerDO updateCustomer = BeanUtils.toBean(importCustomer, CrmCustomerDO.class); + updateCustomer.setId(existCustomer.getId()); + customerMapper.updateById(updateCustomer); + respVO.getUpdateCustomerNames().add(importCustomer.getName()); + }); + return respVO; + } + + private void validateCustomerForCreate(CrmCustomerImportExcelVO importCustomer) { + // 校验客户名称不能为空 + if (StrUtil.isEmptyIfStr(importCustomer.getName())) { + throw exception(CUSTOMER_CREATE_NAME_NOT_NULL); + } + } + + // ==================== 公海相关操作 ==================== @Override @Transactional(rollbackFor = Exception.class)