Commit acb8d3f23b8725c809eb6fd63b38c333866c3692

Authored by 芋道源码
Committed by Gitee
2 parents 6f95f371 4418a7be

!858 CRM-客户: 完善客户导入

Merge pull request !858 from puhui999/develop
Showing 25 changed files with 463 additions and 117 deletions
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmModelController.java
... ... @@ -6,16 +6,15 @@ import cn.iocoder.yudao.framework.common.util.io.IoUtils;
6 6 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
7 7 import cn.iocoder.yudao.module.bpm.convert.definition.BpmModelConvert;
8 8 import cn.iocoder.yudao.module.bpm.service.definition.BpmModelService;
9   -import io.swagger.v3.oas.annotations.tags.Tag;
10   -import io.swagger.v3.oas.annotations.Parameter;
11 9 import io.swagger.v3.oas.annotations.Operation;
  10 +import io.swagger.v3.oas.annotations.Parameter;
  11 +import io.swagger.v3.oas.annotations.tags.Tag;
  12 +import jakarta.annotation.Resource;
  13 +import jakarta.validation.Valid;
12 14 import org.springframework.security.access.prepost.PreAuthorize;
13 15 import org.springframework.validation.annotation.Validated;
14 16 import org.springframework.web.bind.annotation.*;
15 17  
16   -import jakarta.annotation.Resource;
17   -import jakarta.validation.Valid;
18   -
19 18 import java.io.IOException;
20 19  
21 20 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
... ... @@ -44,6 +43,15 @@ public class BpmModelController {
44 43 return success(model);
45 44 }
46 45  
  46 + @GetMapping("/get-by-key")
  47 + @Operation(summary = "获得模型")
  48 + @Parameter(name = "key", description = "流程标识", required = true, example = "oa_leave")
  49 + @PreAuthorize("@ss.hasPermission('bpm:model:query')")
  50 + public CommonResult<BpmModelRespVO> getModelByKey(@RequestParam("key") String key) {
  51 + BpmModelRespVO model = modelService.getBpmnModelByKey(key);
  52 + return success(model);
  53 + }
  54 +
47 55 @PostMapping("/create")
48 56 @Operation(summary = "新建模型")
49 57 @PreAuthorize("@ss.hasPermission('bpm:model:create')")
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java
... ... @@ -2,9 +2,8 @@ package cn.iocoder.yudao.module.bpm.service.definition;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.*;
5   -import org.flowable.bpmn.model.BpmnModel;
6   -
7 5 import jakarta.validation.Valid;
  6 +import org.flowable.bpmn.model.BpmnModel;
8 7  
9 8 /**
10 9 * Flowable流程模型接口
... ... @@ -39,6 +38,14 @@ public interface BpmModelService {
39 38 BpmModelRespVO getModel(String id);
40 39  
41 40 /**
  41 + * 获得流程模块
  42 + *
  43 + * @param key 流程标识
  44 + * @return 流程模型
  45 + */
  46 + BpmModelRespVO getBpmnModelByKey(String key);
  47 +
  48 + /**
42 49 * 修改流程模型
43 50 *
44 51 * @param updateReqVO 更新信息
... ... @@ -62,7 +69,7 @@ public interface BpmModelService {
62 69 /**
63 70 * 修改模型的状态,实际更新的部署的流程定义的状态
64 71 *
65   - * @param id 编号
  72 + * @param id 编号
66 73 * @param state 状态
67 74 */
68 75 void updateModelState(String id, Integer state);
... ...
yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java
... ... @@ -14,6 +14,8 @@ import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmFormDO;
14 14 import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
15 15 import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmModelMetaInfoRespDTO;
16 16 import cn.iocoder.yudao.module.bpm.service.definition.dto.BpmProcessDefinitionCreateReqDTO;
  17 +import jakarta.annotation.Resource;
  18 +import jakarta.validation.Valid;
17 19 import lombok.extern.slf4j.Slf4j;
18 20 import org.flowable.bpmn.converter.BpmnXMLConverter;
19 21 import org.flowable.bpmn.model.BpmnModel;
... ... @@ -29,8 +31,6 @@ import org.springframework.transaction.annotation.Transactional;
29 31 import org.springframework.util.ObjectUtils;
30 32 import org.springframework.validation.annotation.Validated;
31 33  
32   -import jakarta.annotation.Resource;
33   -import jakarta.validation.Valid;
34 34 import java.util.*;
35 35  
36 36 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
... ... @@ -135,6 +135,19 @@ public class BpmModelServiceImpl implements BpmModelService {
135 135 }
136 136  
137 137 @Override
  138 + public BpmModelRespVO getBpmnModelByKey(String key) {
  139 + Model model = getModelByKey(key);
  140 + if (model == null) {
  141 + return null;
  142 + }
  143 + BpmModelRespVO modelRespVO = BpmModelConvert.INSTANCE.convert(model);
  144 + // 拼接 bpmn XML
  145 + byte[] bpmnBytes = repositoryService.getModelEditorSource(model.getId());
  146 + modelRespVO.setBpmnXml(StrUtil.utf8Str(bpmnBytes));
  147 + return modelRespVO;
  148 + }
  149 +
  150 + @Override
138 151 @Transactional(rollbackFor = Exception.class) // 因为进行多个操作,所以开启事务
139 152 public void updateModel(@Valid BpmModelUpdateReqVO updateReqVO) {
140 153 // 校验流程模型存在
... ...
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/ErrorCodeConstants.java
... ... @@ -11,6 +11,7 @@ public interface ErrorCodeConstants {
11 11  
12 12 // ========== 合同管理 1-020-000-000 ==========
13 13 ErrorCode CONTRACT_NOT_EXISTS = new ErrorCode(1_020_000_000, "合同不存在");
  14 + ErrorCode CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_020_000_001, "更新合同失败,原因:禁止编辑");
14 15  
15 16 // ========== 线索管理 1-020-001-000 ==========
16 17 ErrorCode CLUE_NOT_EXISTS = new ErrorCode(1_020_001_000, "线索不存在");
... ... @@ -48,6 +49,9 @@ public interface ErrorCodeConstants {
48 49 ErrorCode CUSTOMER_LOCK_EXCEED_LIMIT = new ErrorCode(1_020_006_009, "锁定客户失败,超出锁定规则上限");
49 50 ErrorCode CUSTOMER_OWNER_EXCEED_LIMIT = new ErrorCode(1_020_006_010, "操作失败,超出客户数拥有上限");
50 51 ErrorCode CUSTOMER_DELETE_FAIL_HAVE_REFERENCE = new ErrorCode(1_020_006_011, "删除客户失败,有关联{}");
  52 + ErrorCode CUSTOMER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_020_006_012, "导入客户数据不能为空!");
  53 + ErrorCode CUSTOMER_CREATE_NAME_NOT_NULL = new ErrorCode(1_020_006_013, "客户名称不能为空!");
  54 + ErrorCode CUSTOMER_NAME_EXISTS = new ErrorCode(1_020_006_014, "已存在名为【{}】的客户!");
51 55  
52 56 // ========== 权限管理 1_020_007_000 ==========
53 57 ErrorCode CRM_PERMISSION_NOT_EXISTS = new ErrorCode(1_020_007_000, "数据权限不存在");
... ...
yudao-module-crm/yudao-module-crm-biz/pom.xml
... ... @@ -27,6 +27,11 @@
27 27 <artifactId>yudao-module-crm-api</artifactId>
28 28 <version>${revision}</version>
29 29 </dependency>
  30 + <dependency>
  31 + <groupId>cn.iocoder.boot</groupId>
  32 + <artifactId>yudao-module-bpm-api</artifactId>
  33 + <version>${revision}</version>
  34 + </dependency>
30 35  
31 36 <!-- 业务组件 -->
32 37 <dependency>
... ... @@ -37,6 +42,10 @@
37 42 <groupId>cn.iocoder.boot</groupId>
38 43 <artifactId>yudao-spring-boot-starter-biz-ip</artifactId>
39 44 </dependency>
  45 + <dependency>
  46 + <groupId>cn.iocoder.boot</groupId>
  47 + <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
  48 + </dependency>
40 49  
41 50 <!-- Web 相关 -->
42 51 <dependency>
... ... @@ -70,11 +79,5 @@
70 79 <groupId>cn.iocoder.boot</groupId>
71 80 <artifactId>yudao-spring-boot-starter-test</artifactId>
72 81 </dependency>
73   -
74   - <!-- TODO @puhui999:放的位置,要整齐哈。 -->
75   - <dependency>
76   - <groupId>cn.iocoder.boot</groupId>
77   - <artifactId>yudao-spring-boot-starter-biz-tenant</artifactId>
78   - </dependency>
79 82 </dependencies>
80 83 </project>
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/CrmContractController.java
... ... @@ -140,4 +140,12 @@ public class CrmContractController {
140 140 return success(true);
141 141 }
142 142  
  143 + @PutMapping("/approve")
  144 + @Operation(summary = "发起合同审批流程")
  145 + @PreAuthorize("@ss.hasPermission('crm:contract:update')")
  146 + public CommonResult<Boolean> transfer(@RequestParam("id") Long id) {
  147 + contractService.handleApprove(id, getLoginUserId());
  148 + return success(true);
  149 + }
  150 +
143 151 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/contract/vo/CrmContractSaveReqVO.java
... ... @@ -7,10 +7,13 @@ import cn.iocoder.yudao.module.crm.framework.operatelog.core.SysAdminUserParseFu
7 7 import com.mzt.logapi.starter.annotation.DiffLogField;
8 8 import io.swagger.v3.oas.annotations.media.Schema;
9 9 import jakarta.validation.constraints.NotNull;
  10 +import lombok.AllArgsConstructor;
10 11 import lombok.Data;
  12 +import lombok.NoArgsConstructor;
11 13 import org.springframework.format.annotation.DateTimeFormat;
12 14  
13 15 import java.time.LocalDateTime;
  16 +import java.util.List;
14 17  
15 18 import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
16 19  
... ... @@ -35,10 +38,6 @@ public class CrmContractSaveReqVO {
35 38 @DiffLogField(name = "商机", function = CrmBusinessParseFunction.NAME)
36 39 private Long businessId;
37 40  
38   - @Schema(description = "工作流编号", example = "1043")
39   - @DiffLogField(name = "工作流编号")
40   - private Long processInstanceId;
41   -
42 41 @Schema(description = "下单日期", requiredMode = Schema.RequiredMode.REQUIRED)
43 42 @DiffLogField(name = "下单日期")
44 43 @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
... ... @@ -86,16 +85,33 @@ public class CrmContractSaveReqVO {
86 85 @DiffLogField(name = "公司签约人", function = SysAdminUserParseFunction.NAME)
87 86 private Long signUserId;
88 87  
89   - @Schema(description = "最后跟进时间")
90   - @DiffLogField(name = "最后跟进时间")
91   - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
92   - private LocalDateTime contactLastTime;
93   -
94 88 @Schema(description = "备注", example = "你猜")
95 89 @DiffLogField(name = "备注")
96 90 private String remark;
97 91  
98   - // TODO @dhb52:增加一个 status 字段:具体有哪些值,你来枚举下;主要页面上有个【草稿】【提交审核】的流程,可以看看。然后要对接工作流,这块也可以看看,不确定的地方问我。
  92 + @Schema(description = "审批状态", example = "1")
  93 + private Integer auditStatus;
  94 +
  95 + @Schema(description = "产品列表")
  96 + private List<CrmContractProductItem> productItems;
  97 +
  98 + @Schema(description = "商品属性")
  99 + @Data
  100 + @NoArgsConstructor
  101 + @AllArgsConstructor
  102 + public static class CrmContractProductItem {
  103 +
  104 + @Schema(description = "产品编号", example = "20529")
  105 + @NotNull(message = "产品编号不能为空")
  106 + private Long id;
  107 +
  108 + @Schema(description = "产品数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8911")
  109 + @NotNull(message = "产品数量不能为空")
  110 + private Integer count;
  111 +
  112 + @Schema(description = "产品折扣")
  113 + private Integer discountPercent;
99 114  
  115 + }
100 116  
101 117 }
... ...
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;
... ... @@ -198,6 +201,36 @@ public class CrmCustomerController {
198 201 BeanUtils.toBean(list, CrmCustomerRespVO.class));
199 202 }
200 203  
  204 + @GetMapping("/get-import-template")
  205 + @Operation(summary = "获得导入客户模板")
  206 + public void importTemplate(HttpServletResponse response) throws IOException {
  207 + // 手动创建导出 demo
  208 + List<CrmCustomerImportExcelVO> list = Arrays.asList(
  209 + CrmCustomerImportExcelVO.builder().name("芋道").industryId(1).level(1).source(1).mobile("15601691300").telephone("")
  210 + .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("")
  211 + .areaId(null).detailAddress("").build(),
  212 + CrmCustomerImportExcelVO.builder().name("源码").industryId(1).level(1).source(1).mobile("15601691300").telephone("")
  213 + .website("https://doc.iocoder.cn/").qq("").wechat("").email("yunai@iocoder.cn").description("").remark("")
  214 + .areaId(null).detailAddress("").build()
  215 + );
  216 + // 输出
  217 + ExcelUtils.write(response, "客户导入模板.xls", "客户列表", CrmCustomerImportExcelVO.class, list);
  218 + }
  219 +
  220 + @PostMapping("/import")
  221 + @Operation(summary = "导入客户")
  222 + @Parameters({
  223 + @Parameter(name = "file", description = "Excel 文件", required = true),
  224 + @Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
  225 + })
  226 + @PreAuthorize("@ss.hasPermission('system:customer:import')")
  227 + public CommonResult<CrmCustomerImportRespVO> importExcel(@RequestParam("file") MultipartFile file,
  228 + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
  229 + List<CrmCustomerImportExcelVO> list = ExcelUtils.read(file, CrmCustomerImportExcelVO.class);
  230 + return success(customerService.importCustomerList(list, updateSupport, getLoginUserId()));
  231 + }
  232 +
  233 +
201 234 @PutMapping("/transfer")
202 235 @Operation(summary = "转移客户")
203 236 @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/dataobject/business/CrmBusinessProductDO.java
... ... @@ -2,13 +2,12 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.business;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 4 import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
  5 +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
5 6 import com.baomidou.mybatisplus.annotation.KeySequence;
6 7 import com.baomidou.mybatisplus.annotation.TableId;
7 8 import com.baomidou.mybatisplus.annotation.TableName;
8 9 import lombok.*;
9 10  
10   -import java.math.BigDecimal;
11   -
12 11 /**
13 12 * 商机产品关联表 DO
14 13 *
... ... @@ -29,14 +28,12 @@ public class CrmBusinessProductDO extends BaseDO {
29 28 */
30 29 @TableId
31 30 private Long id;
32   -
33 31 /**
34 32 * 商机编号
35 33 *
36 34 * 关联 {@link CrmBusinessDO#getId()}
37 35 */
38 36 private Long businessId;
39   -
40 37 /**
41 38 * 产品编号
42 39 *
... ... @@ -50,29 +47,27 @@ public class CrmBusinessProductDO extends BaseDO {
50 47 private Integer price;
51 48  
52 49 /**
53   - * 销售价格
  50 + * 销售价格, 单位:分
54 51 */
55   - private BigDecimal salesPrice;
56   -
  52 + private Integer salesPrice;
57 53 /**
58 54 * 数量
59 55 */
60   - private BigDecimal count;
61   -
62   - // TODO @lzxhqs:改成 discountPercent
  56 + private Integer count;
63 57 /**
64 58 * 折扣
65 59 */
66   - private BigDecimal discountPercent;
67   -
68   - // TODO @lzxhqs:改成 totalPrice;总计价格,和现有项目风格一致;
  60 + private Integer discountPercent;
69 61 /**
70   - * 小计(折扣后价格)
  62 + * 总计价格(折扣后价格)
71 63 */
72   - private BigDecimal totalPrice;
  64 + private Integer totalPrice;
73 65  
74 66 /**
75 67 * 单位
  68 + *
  69 + * 字典 {@link DictTypeConstants#CRM_PRODUCT_UNIT}
76 70 */
77   - private String unit;
  71 + private Integer unit;
  72 +
78 73 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/contract/CrmContractDO.java
... ... @@ -45,7 +45,7 @@ public class CrmContractDO extends BaseDO {
45 45 /**
46 46 * 工作流编号
47 47 */
48   - private Long processInstanceId;
  48 + private String processInstanceId;
49 49 /**
50 50 * 下单日期
51 51 */
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/product/CrmProductDO.java
1 1 package cn.iocoder.yudao.module.crm.dal.dataobject.product;
2 2  
3 3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
  4 +import cn.iocoder.yudao.module.crm.enums.DictTypeConstants;
4 5 import cn.iocoder.yudao.module.crm.enums.product.CrmProductStatusEnum;
5 6 import com.baomidou.mybatisplus.annotation.KeySequence;
6 7 import com.baomidou.mybatisplus.annotation.TableId;
... ... @@ -38,7 +39,7 @@ public class CrmProductDO extends BaseDO {
38 39 /**
39 40 * 单位
40 41 *
41   - * 字典 {@link cn.iocoder.yudao.module.crm.enums.DictTypeConstants#CRM_PRODUCT_UNIT}
  42 + * 字典 {@link DictTypeConstants#CRM_PRODUCT_UNIT}
42 43 */
43 44 private Integer unit;
44 45 /**
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/contract/CrmContractMapper.java
... ... @@ -41,7 +41,7 @@ public interface CrmContractMapper extends BaseMapperX&lt;CrmContractDO&gt; {
41 41 default PageResult<CrmContractDO> selectPage(CrmContractPageReqVO pageReqVO, Long userId) {
42 42 MPJLambdaWrapperX<CrmContractDO> mpjLambdaWrapperX = new MPJLambdaWrapperX<>();
43 43 // 拼接数据权限的查询条件
44   - CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTACT.getType(),
  44 + CrmQueryWrapperUtils.appendPermissionCondition(mpjLambdaWrapperX, CrmBizTypeEnum.CRM_CONTRACT.getType(),
45 45 CrmContractDO::getId, userId, pageReqVO.getSceneType(), Boolean.FALSE);
46 46 // 拼接自身的查询条件
47 47 mpjLambdaWrapperX.selectAll(CrmContractDO.class)
... ... @@ -56,7 +56,7 @@ public interface CrmContractMapper extends BaseMapperX&lt;CrmContractDO&gt; {
56 56 default List<CrmContractDO> selectBatchIds(Collection<Long> ids, Long userId) {
57 57 MPJLambdaWrapperX<CrmContractDO> query = new MPJLambdaWrapperX<>();
58 58 // 构建数据权限连表条件
59   - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTACT.getType(), ids, userId);
  59 + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CONTRACT.getType(), ids, userId);
60 60 // 拼接自身的查询条件
61 61 query.selectAll(CrmContractDO.class).in(CrmContractDO::getId, ids).orderByDesc(CrmContractDO::getId);
62 62 return selectJoinList(CrmContractDO.class, query);
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
... ... @@ -101,11 +101,15 @@ public interface CrmCustomerMapper extends BaseMapperX&lt;CrmCustomerDO&gt; {
101 101 return selectJoinPage(pageReqVO, CrmCustomerDO.class, query);
102 102 }
103 103  
104   - default List<CrmCustomerDO> selectListByLockStatusAndOwnerUserIdNotNull(Boolean lockStatus) {
  104 + default List<CrmCustomerDO> selectListByLockAndDealStatusAndNotPool(Boolean lockStatus, Boolean dealStatus) {
105 105 return selectList(new LambdaQueryWrapper<CrmCustomerDO>()
106 106 .eq(CrmCustomerDO::getLockStatus, lockStatus)
107   - // TODO @puhui999:not null 可以转化成大于 0
108   - .isNotNull(CrmCustomerDO::getOwnerUserId));
  107 + .eq(CrmCustomerDO::getDealStatus, dealStatus)
  108 + .gt(CrmCustomerDO::getOwnerUserId, 0));
  109 + }
  110 +
  111 + default CrmCustomerDO selectByCustomerName(String name) {
  112 + return selectOne(CrmCustomerDO::getName, name);
109 113 }
110 114  
111 115 default PageResult<CrmCustomerDO> selectPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO,
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java
1 1 package cn.iocoder.yudao.module.crm.framework.permission.core.util;
2 2  
3   -import cn.hutool.core.util.ObjUtil;
4 3 import cn.hutool.extra.spring.SpringUtil;
5   -import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO;
6   -import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
7   -import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
8 4 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum;
9   -import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
10 5 import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
11 6  
12   -import java.util.List;
13   -
14   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
15 7 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
16 8  
17 9 /**
... ... @@ -30,22 +22,6 @@ public class CrmPermissionUtils {
30 22 return SingletonManager.getPermissionApi().hasAnyRoles(getLoginUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode());
31 23 }
32 24  
33   - // TODO @puhui999:这个貌似直接放到 CrmPermissionService 会更好?
34   - /**
35   - * 校验权限
36   - *
37   - * @param bizType 数据类型,关联 {@link CrmBizTypeEnum}
38   - * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
39   - * @param userId 用户编号
40   - * @param levelEnum 权限级别
41   - * @return boolean
42   - */
43   - public static boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum levelEnum) {
44   - List<CrmPermissionDO> permissionList = SingletonManager.getCrmPermissionService().getPermissionListByBiz(bizType, bizId);
45   - return anyMatch(permissionList, permission ->
46   - ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), levelEnum.getLevel()));
47   - }
48   -
49 25 /**
50 26 * 静态内部类实现单例获取
51 27 *
... ... @@ -54,16 +30,11 @@ public class CrmPermissionUtils {
54 30 private static class SingletonManager {
55 31  
56 32 private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class);
57   - private static final CrmPermissionService CRM_PERMISSION_SERVICE = SpringUtil.getBean(CrmPermissionService.class);
58 33  
59 34 public static PermissionApi getPermissionApi() {
60 35 return PERMISSION_API;
61 36 }
62 37  
63   - public static CrmPermissionService getCrmPermissionService() {
64   - return CRM_PERMISSION_SERVICE;
65   - }
66   -
67 38 }
68 39  
69 40 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java
... ... @@ -20,7 +20,7 @@ public class CrmCustomerAutoPutPoolJob implements JobHandler {
20 20 @Override
21 21 @TenantJob
22 22 public String execute(String param) {
23   - int count = customerService.customerAutoPutPoolBySystem();
  23 + int count = customerService.autoPutCustomerPool();
24 24 return String.format("掉入公海客户 %s 个", count);
25 25 }
26 26  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractService.java
... ... @@ -58,6 +58,14 @@ public interface CrmContractService {
58 58 void updateContractFollowUp(CrmUpdateFollowUpReqBO contractUpdateFollowUpReqBO);
59 59  
60 60 /**
  61 + * 发起合同审批流程
  62 + *
  63 + * @param id 合同编号
  64 + * @param userId 用户编号
  65 + */
  66 + void handleApprove(Long id, Long userId);
  67 +
  68 + /**
61 69 * 获得合同
62 70 *
63 71 * @param id 编号
... ... @@ -111,9 +119,11 @@ public interface CrmContractService {
111 119 Long getContractCountByCustomerId(Long customerId);
112 120  
113 121 /**
114   - * 根据商机ID获取关联客户的合同数量 TODO @lzxhqs:1)方法注释,和参数注释之间要有空行;2)中英文之间有空格,更清晰,例如说 商机 ID
115   - * @param businessId 商机ID
  122 + * 根据商机ID获取关联客户的合同数量
  123 + *
  124 + * @param businessId 商机编号
116 125 * @return 数量
117 126 */
118 127 Long selectCountByBusinessId(Long businessId);
  128 +
119 129 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/CrmContractServiceImpl.java
... ... @@ -2,20 +2,33 @@ package cn.iocoder.yudao.module.crm.service.contract;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.hutool.core.collection.ListUtil;
  5 +import cn.hutool.core.util.ObjUtil;
5 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
6 8 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  9 +import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
  10 +import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
7 11 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractPageReqVO;
8 12 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractSaveReqVO;
9 13 import cn.iocoder.yudao.module.crm.controller.admin.contract.vo.CrmContractTransferReqVO;
10 14 import cn.iocoder.yudao.module.crm.convert.contract.CrmContractConvert;
  15 +import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessProductDO;
11 16 import cn.iocoder.yudao.module.crm.dal.dataobject.contract.CrmContractDO;
  17 +import cn.iocoder.yudao.module.crm.dal.dataobject.product.CrmProductDO;
12 18 import cn.iocoder.yudao.module.crm.dal.mysql.contract.CrmContractMapper;
  19 +import cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum;
13 20 import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum;
14 21 import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum;
15 22 import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission;
  23 +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessProductService;
  24 +import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService;
  25 +import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
  26 +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
16 27 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO;
17 28 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
18 29 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
  30 +import cn.iocoder.yudao.module.crm.service.product.CrmProductService;
  31 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
19 32 import com.mzt.logapi.context.LogRecordContext;
20 33 import com.mzt.logapi.service.impl.DiffParseFunction;
21 34 import com.mzt.logapi.starter.annotation.LogRecord;
... ... @@ -26,10 +39,14 @@ import org.springframework.validation.annotation.Validated;
26 39  
27 40 import java.util.Collection;
28 41 import java.util.List;
  42 +import java.util.Map;
  43 +import java.util.Set;
29 44  
30 45 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
31   -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTRACT_NOT_EXISTS;
  46 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
  47 +import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
32 48 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
  49 +import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.USER_NOT_EXISTS;
33 50  
34 51 /**
35 52 * CRM 合同 Service 实现类
... ... @@ -40,21 +57,36 @@ import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
40 57 @Validated
41 58 public class CrmContractServiceImpl implements CrmContractService {
42 59  
  60 + public static final String CONTRACT_APPROVE = "contract-approve"; // 合同审批流程标识
  61 +
43 62 @Resource
44 63 private CrmContractMapper contractMapper;
45 64  
46 65 @Resource
47 66 private CrmPermissionService crmPermissionService;
  67 + @Resource
  68 + private CrmBusinessProductService businessProductService;
  69 + @Resource
  70 + private CrmProductService productService;
  71 + @Resource
  72 + private BpmProcessInstanceApi bpmProcessInstanceApi;
  73 + @Resource
  74 + private CrmCustomerService customerService;
  75 + @Resource
  76 + private CrmContactService contactService;
  77 + @Resource
  78 + private CrmBusinessService businessService;
  79 + @Resource
  80 + private AdminUserApi adminUserApi;
48 81  
49 82 @Override
50 83 @Transactional(rollbackFor = Exception.class)
51 84 @LogRecord(type = CRM_CONTRACT_TYPE, subType = CRM_CONTRACT_CREATE_SUB_TYPE, bizNo = "{{#contract.id}}",
52 85 success = CRM_CONTRACT_CREATE_SUCCESS)
53 86 public Long createContract(CrmContractSaveReqVO createReqVO, Long userId) {
54   - createReqVO.setId(null);
55   - // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO
  87 + validateRelationDataExists(createReqVO);
56 88 // 插入合同
57   - CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class);
  89 + CrmContractDO contract = BeanUtils.toBean(createReqVO, CrmContractDO.class).setId(null);
58 90 contractMapper.insert(contract);
59 91  
60 92 // 创建数据权限
... ... @@ -62,6 +94,9 @@ public class CrmContractServiceImpl implements CrmContractService {
62 94 .setBizType(CrmBizTypeEnum.CRM_CONTRACT.getType()).setBizId(contract.getId())
63 95 .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
64 96  
  97 + // 插入商机关联商品
  98 + List<CrmBusinessProductDO> businessProduct = convertBusinessProductList(createReqVO);
  99 + businessProductService.insertBatch(businessProduct);
65 100 // 4. 记录操作日志上下文
66 101 LogRecordContext.putVariable("contract", contract);
67 102 return contract.getId();
... ... @@ -74,12 +109,21 @@ public class CrmContractServiceImpl implements CrmContractService {
74 109 @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTRACT, bizId = "#updateReqVO.id", level = CrmPermissionLevelEnum.WRITE)
75 110 public void updateContract(CrmContractSaveReqVO updateReqVO) {
76 111 // TODO @合同待定:只有草稿、审批中,可以编辑;
  112 + if (ObjUtil.notEqual(updateReqVO.getAuditStatus(), CrmAuditStatusEnum.DRAFT.getStatus()) ||
  113 + ObjUtil.notEqual(updateReqVO.getAuditStatus(), CrmAuditStatusEnum.PROCESS.getStatus())) {
  114 + throw exception(CONTRACT_UPDATE_FAIL_EDITING_PROHIBITED);
  115 + }
  116 + validateRelationDataExists(updateReqVO);
77 117 // 校验存在
78 118 CrmContractDO oldContract = validateContractExists(updateReqVO.getId());
79 119 // 更新合同
80 120 CrmContractDO updateObj = BeanUtils.toBean(updateReqVO, CrmContractDO.class);
81 121 contractMapper.updateById(updateObj);
82   - // TODO @合同待定:插入合同商品;需要搞个 BusinessProductDO
  122 +
  123 + // TODO puhui999: @芋艿:合同变更关联的商机后商品怎么处理?
  124 + //List<CrmBusinessProductDO> businessProduct = convertBusinessProductList(updateReqVO);
  125 + //businessProductService.selectListByBusinessId()
  126 + //diffList()
83 127  
84 128 // 3. 记录操作日志上下文
85 129 LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldContract, CrmContractSaveReqVO.class));
... ... @@ -88,8 +132,55 @@ public class CrmContractServiceImpl implements CrmContractService {
88 132  
89 133 // TODO @合同待定:缺一个取消合同的接口;只有草稿、审批中可以取消;CrmAuditStatusEnum
90 134  
91   - // TODO @合同待定:缺一个发起审批的接口;只有草稿可以发起审批;CrmAuditStatusEnum
  135 + private List<CrmBusinessProductDO> convertBusinessProductList(CrmContractSaveReqVO reqVO) {
  136 + // 校验商品存在
  137 + Set<Long> productIds = convertSet(reqVO.getProductItems(), CrmContractSaveReqVO.CrmContractProductItem::getId);
  138 + List<CrmProductDO> productList = productService.getProductList(productIds);
  139 + if (CollUtil.isEmpty(productIds) || productList.size() != productIds.size()) {
  140 + throw exception(PRODUCT_NOT_EXISTS);
  141 + }
  142 + Map<Long, CrmProductDO> productMap = convertMap(productList, CrmProductDO::getId);
  143 + return convertList(reqVO.getProductItems(), productItem -> {
  144 + CrmBusinessProductDO businessProduct = BeanUtils.toBean(productMap.get(productItem.getId()), CrmBusinessProductDO.class);
  145 + businessProduct.setId(null).setBusinessId(reqVO.getBusinessId()).setProductId(productItem.getId())
  146 + .setCount(productItem.getCount()).setDiscountPercent(productItem.getDiscountPercent()).setTotalPrice(calculator(businessProduct));
  147 + return businessProduct;
  148 + });
  149 + }
92 150  
  151 + /**
  152 + * 计算商品总价
  153 + *
  154 + * @param businessProduct 关联商品
  155 + * @return 商品总价
  156 + */
  157 + private Integer calculator(CrmBusinessProductDO businessProduct) {
  158 + int price = businessProduct.getPrice() * businessProduct.getCount();
  159 + if (businessProduct.getDiscountPercent() == null) {
  160 + return price;
  161 + }
  162 + return MoneyUtils.calculateRatePriceFloor(price, (double) (businessProduct.getDiscountPercent() / 100));
  163 + }
  164 +
  165 + /**
  166 + * 校验关联数据是否存在
  167 + *
  168 + * @param reqVO 请求
  169 + */
  170 + private void validateRelationDataExists(CrmContractSaveReqVO reqVO) {
  171 + // 1. 校验客户
  172 + if (reqVO.getCustomerId() != null && customerService.getCustomer(reqVO.getCustomerId()) == null) {
  173 + throw exception(CUSTOMER_NOT_EXISTS);
  174 + }
  175 + // 2. 校验负责人
  176 + if (reqVO.getOwnerUserId() != null && adminUserApi.getUser(reqVO.getOwnerUserId()) == null) {
  177 + throw exception(USER_NOT_EXISTS);
  178 + }
  179 + // 4. 如果有关联商机,则需要校验存在
  180 + if (reqVO.getBusinessId() != null && businessService.getBusiness(reqVO.getBusinessId()) == null) {
  181 + throw exception(BUSINESS_NOT_EXISTS);
  182 + }
  183 + }
93 184  
94 185 @Override
95 186 @Transactional(rollbackFor = Exception.class)
... ... @@ -141,6 +232,18 @@ public class CrmContractServiceImpl implements CrmContractService {
141 232 contractMapper.updateById(BeanUtils.toBean(contractUpdateFollowUpReqBO, CrmContractDO.class).setId(contractUpdateFollowUpReqBO.getBizId()));
142 233 }
143 234  
  235 + @Override
  236 + @Transactional(rollbackFor = Exception.class)
  237 + public void handleApprove(Long id, Long userId) {
  238 + // 创建合同审批流程实例
  239 + String processInstanceId = bpmProcessInstanceApi.createProcessInstance(userId, new BpmProcessInstanceCreateReqDTO()
  240 + .setProcessDefinitionKey(CONTRACT_APPROVE).setBusinessKey(String.valueOf(id)));
  241 +
  242 + // 更新合同工作流编号
  243 + contractMapper.updateById(new CrmContractDO().setId(id).setProcessInstanceId(processInstanceId)
  244 + .setAuditStatus(CrmAuditStatusEnum.PROCESS.getStatus()));
  245 + }
  246 +
144 247 //======================= 查询相关 =======================
145 248  
146 249 @Override
... ... @@ -182,6 +285,5 @@ public class CrmContractServiceImpl implements CrmContractService {
182 285 public Long selectCountByBusinessId(Long businessId) {
183 286 return contractMapper.selectCountByBusinessId(businessId);
184 287 }
185   -
186 288 // TODO @合同待定:需要新增一个 ContractConfigDO 表,合同配置,重点是到期提醒;
187 289 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractResultListener.java 0 → 100644
  1 +package cn.iocoder.yudao.module.crm.service.contract.listener;
  2 +
  3 +public class CrmContractResultListener {
  4 + // TODO puhui999: @芋艿: 艿艿写一下这个,没研究明白哈哈
  5 +}
... ...
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.dal.dataobject.customer.CrmCustomerPoolConfigDO;
10 7 import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO;
... ... @@ -109,6 +106,16 @@ public interface CrmCustomerService {
109 106 */
110 107 Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId);
111 108  
  109 + /**
  110 + * 批量导入客户
  111 + *
  112 + * @param importCustomers 导入客户列表
  113 + * @param isUpdateSupport 是否支持更新
  114 + * @param userId 用户编号
  115 + * @return 导入结果
  116 + */
  117 + CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers, Boolean isUpdateSupport, Long userId);
  118 +
112 119 // ==================== 公海相关操作 ====================
113 120  
114 121 /**
... ... @@ -127,13 +134,12 @@ public interface CrmCustomerService {
127 134 */
128 135 void receiveCustomer(List<Long> ids, Long ownerUserId, Boolean isReceive);
129 136  
130   - // TODO @puhui999:autoPutCustomerPool,注释说明是系统就好哈;
131 137 /**
132 138 * 【系统】客户自动掉入公海
133 139 *
134 140 * @return 掉入公海数量
135 141 */
136   - int customerAutoPutPoolBySystem();
  142 + int autoPutCustomerPool();
137 143  
138 144 PageResult<CrmCustomerDO> getPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO,
139 145 CrmCustomerPoolConfigDO poolConfigDO,
... ...
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,12 +40,10 @@ 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;
  46 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList;
50 47 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
51 48 import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*;
52 49 import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT;
... ... @@ -96,7 +93,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
96 93 validateCustomerExceedOwnerLimit(createReqVO.getOwnerUserId(), 1);
97 94  
98 95 // 2. 插入客户
99   - CrmCustomerDO customer = BeanUtils.toBean(createReqVO, CrmCustomerDO.class)
  96 + CrmCustomerDO customer = BeanUtils.toBean(createReqVO, CrmCustomerDO.class).setOwnerUserId(userId)
100 97 .setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now());
101 98 customerMapper.insert(customer);
102 99  
... ... @@ -235,7 +232,55 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
235 232 return customer.getId();
236 233 }
237 234  
238   -// ==================== 公海相关操作 ====================
  235 + @Override
  236 + public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers, Boolean isUpdateSupport, Long userId) {
  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 + CrmCustomerDO customer = BeanUtils.toBean(importCustomer, CrmCustomerDO.class).setOwnerUserId(userId)
  254 + .setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now());
  255 + customerMapper.insert(customer);
  256 + respVO.getCreateCustomerNames().add(importCustomer.getName());
  257 + // 创建数据权限
  258 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType())
  259 + .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人
  260 + return;
  261 + }
  262 + // 如果存在,判断是否允许更新
  263 + if (!isUpdateSupport) {
  264 + respVO.getFailureCustomerNames().put(importCustomer.getName(),
  265 + StrUtil.format(CUSTOMER_NAME_EXISTS.getMsg(), importCustomer.getName()));
  266 + return;
  267 + }
  268 + CrmCustomerDO updateCustomer = BeanUtils.toBean(importCustomer, CrmCustomerDO.class);
  269 + updateCustomer.setId(existCustomer.getId());
  270 + customerMapper.updateById(updateCustomer);
  271 + respVO.getUpdateCustomerNames().add(importCustomer.getName());
  272 + });
  273 + return respVO;
  274 + }
  275 +
  276 + private void validateCustomerForCreate(CrmCustomerImportExcelVO importCustomer) {
  277 + // 校验客户名称不能为空
  278 + if (StrUtil.isEmptyIfStr(importCustomer.getName())) {
  279 + throw exception(CUSTOMER_CREATE_NAME_NOT_NULL);
  280 + }
  281 + }
  282 +
  283 + // ==================== 公海相关操作 ====================
239 284  
240 285 @Override
241 286 @Transactional(rollbackFor = Exception.class)
... ... @@ -313,23 +358,22 @@ public class CrmCustomerServiceImpl implements CrmCustomerService {
313 358 }
314 359  
315 360 @Override
316   - public int customerAutoPutPoolBySystem() {
  361 + public int autoPutCustomerPool() {
317 362 CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig();
318 363 if (poolConfig == null || !poolConfig.getEnabled()) {
319 364 return 0;
320 365 }
321   - // 1. 获取没有锁定的不在公海的客户
322   - List<CrmCustomerDO> customerList = customerMapper.selectListByLockStatusAndOwnerUserIdNotNull(Boolean.FALSE);
323   - List<CrmCustomerDO> poolCustomerList = CollectionUtils.filterList(customerList, customer -> {
324   - // TODO @puhui999:建议这里作为一个查询条件哈,不放内存里过滤;
325   - // 1.1 未成交放入公海
326   - if (!customer.getDealStatus()) {
327   - return (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0;
328   - }
329   - // 1.2 未跟进放入公海
  366 + // 1.1 获取没有锁定的不在公海的客户且没有成交的
  367 + List<CrmCustomerDO> notDealCustomerList = customerMapper.selectListByLockAndDealStatusAndNotPool(Boolean.FALSE, Boolean.FALSE);
  368 + // 1.2 获取没有锁定的不在公海的客户且成交的
  369 + List<CrmCustomerDO> dealCustomerList = customerMapper.selectListByLockAndDealStatusAndNotPool(Boolean.FALSE, Boolean.TRUE);
  370 + List<CrmCustomerDO> poolCustomerList = new ArrayList<>();
  371 + poolCustomerList.addAll(filterList(notDealCustomerList, customer ->
  372 + (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0));
  373 + poolCustomerList.addAll(filterList(dealCustomerList, customer -> {
330 374 LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime());
331 375 return (poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime)) <= 0;
332   - });
  376 + }));
333 377  
334 378 // 2. 逐个放入公海
335 379 int count = 0;
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java
... ... @@ -33,7 +33,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU
33 33 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
34 34 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_DELETE_DENIED;
35 35 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_NOT_EXISTS;
36   -import static cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils.hasPermission;
37 36  
38 37 /**
39 38 * 跟进记录 Service 实现类
... ... @@ -119,7 +118,7 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService {
119 118 // 校验存在
120 119 CrmFollowUpRecordDO followUpRecord = validateFollowUpRecordExists(id);
121 120 // 校验权限
122   - if (!hasPermission(followUpRecord.getBizType(), followUpRecord.getBizId(), userId, CrmPermissionLevelEnum.OWNER)) {
  121 + if (!permissionService.hasPermission(followUpRecord.getBizType(), followUpRecord.getBizId(), userId, CrmPermissionLevelEnum.OWNER)) {
123 122 throw exception(FOLLOW_UP_RECORD_DELETE_DENIED);
124 123 }
125 124  
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionService.java
... ... @@ -108,4 +108,15 @@ public interface CrmPermissionService {
108 108 */
109 109 List<CrmPermissionDO> getPermissionListByBizTypeAndUserId(Integer bizType, Long userId);
110 110  
  111 + /**
  112 + * 校验权限
  113 + *
  114 + * @param bizType 数据类型,关联 {@link CrmBizTypeEnum}
  115 + * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId()
  116 + * @param userId 用户编号
  117 + * @param levelEnum 权限级别
  118 + * @return boolean
  119 + */
  120 + boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum levelEnum);
  121 +
111 122 }
... ...
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/permission/CrmPermissionServiceImpl.java
... ... @@ -24,6 +24,7 @@ import java.util.List;
24 24 import java.util.Set;
25 25  
26 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  27 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch;
27 28 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
28 29 import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*;
29 30 import static cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum.isOwner;
... ... @@ -211,4 +212,11 @@ public class CrmPermissionServiceImpl implements CrmPermissionService {
211 212 return permissionMapper.selectListByBizTypeAndUserId(bizType, userId);
212 213 }
213 214  
  215 + @Override
  216 + public boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum levelEnum) {
  217 + List<CrmPermissionDO> permissionList = permissionMapper.selectByBizTypeAndBizId(bizType, bizId);
  218 + return anyMatch(permissionList, permission ->
  219 + ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), levelEnum.getLevel()));
  220 + }
  221 +
214 222 }
... ...