From 6850c7c71d236f73a99658a0af04b98e9bdb2e6b Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 23 Jan 2024 22:31:51 +0800 Subject: [PATCH 1/2] =?UTF-8?q?CRM:=20=E5=AE=8C=E5=96=84=E8=B7=9F=E8=BF=9B?= =?UTF-8?q?=E6=97=A5=E5=BF=97=20TODO?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../operatelog/CrmOperateLogController.java | 12 ++- .../operatelog/vo/CrmOperateLogV2RespVO.java | 89 +++++++++++++++++++ .../permission/CrmPermissionConvert.java | 8 +- .../dataobject/customer/CrmCustomerDO.java | 5 ++ .../core/util/CrmPermissionUtils.java | 28 ++++++ .../crm/service/clue/CrmClueServiceImpl.java | 37 +++----- .../CrmContactBusinessServiceImpl.java | 10 ++- .../service/customer/CrmCustomerService.java | 8 +- .../customer/CrmCustomerServiceImpl.java | 34 ++++--- .../CrmFollowUpRecordServiceImpl.java | 20 ++--- .../followup/bo/CrmUpdateFollowUpReqBO.java | 2 - 11 files changed, 178 insertions(+), 75 deletions(-) create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java index 2290d73079..982ad3c0b1 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/CrmOperateLogController.java @@ -2,14 +2,14 @@ package cn.iocoder.yudao.module.crm.controller.admin.operatelog; import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogPageReqVO; +import cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo.CrmOperateLogV2RespVO; import cn.iocoder.yudao.module.crm.enums.LogRecordConstants; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.system.api.logger.OperateLogApi; import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2PageReqDTO; -import cn.iocoder.yudao.module.system.api.logger.dto.OperateLogV2RespDTO; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; @@ -51,16 +51,14 @@ public class CrmOperateLogController { BIZ_TYPE_MAP.put(CrmBizTypeEnum.CRM_RECEIVABLE_PLAN.getType(), CRM_RECEIVABLE_PLAN_TYPE); } - // TODO @puhui999:还是搞个 VO 出来哈 @GetMapping("/page") @Operation(summary = "获得操作日志") - @Parameter(name = "id", description = "客户编号", required = true) - @PreAuthorize("@ss.hasPermission('crm:customer:query')") - public CommonResult> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) { + @PreAuthorize("@ss.hasPermission('crm:operate-log:query')") + public CommonResult> getCustomerOperateLog(@Valid CrmOperateLogPageReqVO pageReqVO) { OperateLogV2PageReqDTO reqDTO = new OperateLogV2PageReqDTO(); reqDTO.setPageSize(PAGE_SIZE_NONE); // 默认不分页,需要分页需注释 reqDTO.setBizType(BIZ_TYPE_MAP.get(pageReqVO.getBizType())).setBizId(pageReqVO.getBizId()); - return success(operateLogApi.getOperateLogPage(reqDTO)); + return success(BeanUtils.toBean(operateLogApi.getOperateLogPage(reqDTO), CrmOperateLogV2RespVO.class)); } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java new file mode 100644 index 0000000000..298b5a51d5 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/operatelog/vo/CrmOperateLogV2RespVO.java @@ -0,0 +1,89 @@ +package cn.iocoder.yudao.module.crm.controller.admin.operatelog.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - CRM 跟进 Response VO") +@Data +@ExcelIgnoreUnannotated +public class CrmOperateLogV2RespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private Long id; + /** + * 链路追踪编号 + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private String traceId; + /** + * 用户编号 + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long userId; + /** + * 用户名称 + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿") + private String userName; + /** + * 用户类型 + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer userType; + /** + * 操作模块类型 + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private String type; + /** + * 操作名 + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "修改客户") + private String subType; + /** + * 操作模块业务编号 + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private Long bizId; + /** + * 操作内容 + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "将什么从什么改为了什么") + private String action; + /** + * 拓展字段 + */ + @Schema(description = "编号", example = "{orderId: 1}") + private String extra; + + /** + * 请求方法名 + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private String requestMethod; + /** + * 请求地址 + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private String requestUrl; + /** + * 用户 IP + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private String userIp; + /** + * 浏览器 UA + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13563") + private String userAgent; + + /** + * 创建时间 + */ + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-01-01") + private LocalDateTime createTime; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java index d6c36f176b..f51544caea 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/convert/permission/CrmPermissionConvert.java @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.crm.convert.permission; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.collection.MapUtils; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionRespVO; import cn.iocoder.yudao.module.crm.controller.admin.permission.vo.CrmPermissionUpdateReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; @@ -29,13 +30,10 @@ public interface CrmPermissionConvert { CrmPermissionConvert INSTANCE = Mappers.getMapper(CrmPermissionConvert.class); - // TODO @puhui999:这个要不也搞到 copy 里 - List convert(List permission); - - default List convert(List permission, List userList, + default List convert(List permissions, List userList, Map deptMap, Map postMap) { Map userMap = CollectionUtils.convertMap(userList, AdminUserRespDTO::getId); - return CollectionUtils.convertList(convert(permission), item -> { + return CollectionUtils.convertList(BeanUtils.toBean(permissions, CrmPermissionRespVO.class), item -> { findAndThen(userMap, item.getUserId(), user -> { item.setNickname(user.getNickname()); findAndThen(deptMap, user.getDeptId(), deptRespDTO -> item.setDeptName(deptRespDTO.getName())); diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java index 96e4bf5200..2bd614f571 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java @@ -114,10 +114,15 @@ public class CrmCustomerDO extends BaseDO { */ private String detailAddress; + /** + * 最后接收时间 + */ + private LocalDateTime receiveTime; /** * 最后跟进时间 */ private LocalDateTime contactLastTime; + /** * 最后跟进内容 */ diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java index 43bb729b6c..8f9c194108 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/framework/permission/core/util/CrmPermissionUtils.java @@ -1,9 +1,17 @@ package cn.iocoder.yudao.module.crm.framework.permission.core.util; +import cn.hutool.core.util.ObjUtil; import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionRoleCodeEnum; +import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; import cn.iocoder.yudao.module.system.api.permission.PermissionApi; +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch; import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; /** @@ -22,6 +30,21 @@ public class CrmPermissionUtils { return SingletonManager.getPermissionApi().hasAnyRoles(getLoginUserId(), CrmPermissionRoleCodeEnum.CRM_ADMIN.getCode()); } + /** + * 校验权限 + * + * @param bizType 数据类型,关联 {@link CrmBizTypeEnum} + * @param bizId 数据编号,关联 {@link CrmBizTypeEnum} 对应模块 DO#getId() + * @param userId 用户编号 + * @param levelEnum 权限级别 + * @return boolean + */ + public static boolean hasPermission(Integer bizType, Long bizId, Long userId, CrmPermissionLevelEnum levelEnum) { + List permissionList = SingletonManager.getCrmPermissionService().getPermissionListByBiz(bizType, bizId); + return anyMatch(permissionList, permission -> + ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), levelEnum.getLevel())); + } + /** * 静态内部类实现单例获取 * @@ -30,11 +53,16 @@ public class CrmPermissionUtils { private static class SingletonManager { private static final PermissionApi PERMISSION_API = SpringUtil.getBean(PermissionApi.class); + private static final CrmPermissionService CRM_PERMISSION_SERVICE = SpringUtil.getBean(CrmPermissionService.class); public static PermissionApi getPermissionApi() { return PERMISSION_API; } + public static CrmPermissionService getCrmPermissionService() { + return CRM_PERMISSION_SERVICE; + } + } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java index 304b150fe3..2329c4dce1 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.crm.service.clue; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; @@ -15,7 +14,6 @@ import cn.iocoder.yudao.module.crm.controller.admin.clue.vo.CrmClueTransformReqV import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerSaveReqVO; import cn.iocoder.yudao.module.crm.convert.clue.CrmClueConvert; import cn.iocoder.yudao.module.crm.dal.dataobject.clue.CrmClueDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; import cn.iocoder.yudao.module.crm.dal.mysql.clue.CrmClueMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; @@ -193,46 +191,39 @@ public class CrmClueServiceImpl implements CrmClueService { } // 2. 遍历线索(未转化的线索),创建对应的客户 - // TODO @puhui999:这里不用过滤了; - List translateClues = filterList(clues, clue -> ObjUtil.equal(Boolean.FALSE, clue.getTransformStatus())); - List customers = customerService.createCustomerBatch(convertList(translateClues, clue -> - BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class)), userId); + clues.forEach(clue -> { + Long customerId = customerService.createCustomer(BeanUtils.toBean(clue, CrmCustomerCreateReqBO.class), userId); + clue.setCustomerId(customerId); + }); - // TODO @puhui999:这里不用搞一个 clueCustomerIdMap 出来;可以考虑逐个创建,然后把 customerId 设置回 CrmClueDO;避免 name 匹配,极端会有问题哈; - // TODO 是不是就直接 foreach 处理好了;因为本身量不大,for 处理性能 ok,可阅读性好 - Map clueCustomerIdMap = new HashMap<>(translateClues.size()); // 2.1 更新线索 - clueMapper.updateBatch(convertList(customers, customer -> { - CrmClueDO firstClue = findFirst(translateClues, clue -> ObjUtil.equal(clue.getName(), customer.getName())); - clueCustomerIdMap.put(firstClue.getId(), customer.getId()); - return new CrmClueDO().setId(firstClue.getId()).setTransformStatus(Boolean.TRUE).setCustomerId(customer.getId()); - })); + clueMapper.updateBatch(convertList(clues, clue -> new CrmClueDO().setId(clue.getId()).setTransformStatus(Boolean.TRUE) + .setCustomerId(clue.getCustomerId()))); // 2.3 复制跟进 - updateFollowUpRecords(clueCustomerIdMap); - + updateFollowUpRecords(clues); // 3. 记录操作日志 - for (CrmClueDO clue : translateClues) { - // TODO @puhui999:这里优化下,translate 操作日志 - getSelf().receiveClueLog(clue); + for (CrmClueDO clue : clues) { + getSelf().translateCustomerLog(clue); } } - private void updateFollowUpRecords(Map clueCustomerIdMap) { + private void updateFollowUpRecords(List clues) { List followUpRecords = followUpRecordService.getFollowUpRecordByBiz( - CrmBizTypeEnum.CRM_LEADS.getType(), clueCustomerIdMap.keySet()); + CrmBizTypeEnum.CRM_LEADS.getType(), convertSet(clues, CrmClueDO::getId)); if (CollUtil.isEmpty(followUpRecords)) { return; } + Map clueMap = convertMap(clues, CrmClueDO::getId); // 创建跟进 followUpRecordService.createFollowUpRecordBatch(convertList(followUpRecords, followUpRecord -> BeanUtils.toBean(followUpRecord, CrmFollowUpCreateReqBO.class).setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) - .setBizId(clueCustomerIdMap.get(followUpRecord.getBizId())))); + .setBizId(clueMap.get(followUpRecord.getBizId()).getCustomerId()))); } @LogRecord(type = CRM_LEADS_TYPE, subType = CRM_LEADS_TRANSLATE_SUB_TYPE, bizNo = "{{#clue.id}}", success = CRM_LEADS_TRANSLATE_SUCCESS) - public void receiveClueLog(CrmClueDO clue) { + public void translateCustomerLog(CrmClueDO clue) { // 记录操作日志上下文 LogRecordContext.putVariable("clue", clue); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java index 10b6a2ec30..7b07113346 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/contact/CrmContactBusinessServiceImpl.java @@ -6,6 +6,9 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.business.CrmBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactBusinessDO; import cn.iocoder.yudao.module.crm.dal.dataobject.contact.CrmContactDO; import cn.iocoder.yudao.module.crm.dal.mysql.contactbusinesslink.CrmContactBusinessMapper; +import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; +import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; +import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; import jakarta.annotation.Resource; import org.springframework.context.annotation.Lazy; @@ -19,7 +22,6 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.BUSINESS_NOT_EXISTS; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CONTACT_NOT_EXISTS; -// TODO @puhui999:数据权限的校验;每个操作; /** * 联系人与商机的关联 Service 实现类 * @@ -40,6 +42,7 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService private CrmContactService contactService; @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#createReqVO.contactId", level = CrmPermissionLevelEnum.WRITE) public void createContactBusinessList(CrmContactBusinessReqVO createReqVO) { CrmContactDO contact = contactService.getContact(createReqVO.getContactId()); if (contact == null) { @@ -65,6 +68,7 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService } @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#deleteReqVO.contactId", level = CrmPermissionLevelEnum.WRITE) public void deleteContactBusinessList(CrmContactBusinessReqVO deleteReqVO) { CrmContactDO contact = contactService.getContact(deleteReqVO.getContactId()); if (contact == null) { @@ -76,11 +80,13 @@ public class CrmContactBusinessServiceImpl implements CrmContactBusinessService } @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.WRITE) public void deleteContactBusinessByContactId(Long contactId) { - contactBusinessMapper.delete(CrmContactBusinessDO::getContactId,contactId); + contactBusinessMapper.delete(CrmContactBusinessDO::getContactId, contactId); } @Override + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CONTACT, bizId = "#contactId", level = CrmPermissionLevelEnum.READ) public List getContactBusinessListByContactId(Long contactId) { return contactBusinessMapper.selectListByContactId(contactId); } 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 38d5f082b3..d813b2b4d1 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 @@ -100,13 +100,13 @@ public interface CrmCustomerService { void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO); /** - * 批量创建客户 + * 创建客户 * - * @param customerCreateReqBOs 请求 - * @param userId 用户编号 + * @param customerCreateReq 请求信息 + * @param userId 用户编号 * @return 客户列表 */ - List createCustomerBatch(List customerCreateReqBOs, Long userId); + Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId); // ==================== 公海相关操作 ==================== 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 45893fa873..2958dbdf34 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 @@ -43,12 +43,10 @@ import java.util.Collections; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT; import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_OWNER_LIMIT; -import static java.util.Collections.emptyList; import static java.util.Collections.singletonList; /** @@ -91,9 +89,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { // 2. 插入客户 CrmCustomerDO customer = BeanUtils.toBean(createReqVO, CrmCustomerDO.class) - .setLockStatus(false).setDealStatus(false) - .setContactLastTime(LocalDateTime.now()); - // TODO @puhui999:可能要加个 receiveTime 字段,记录最后接收时间 + .setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now()); customerMapper.insert(customer); // 3. 创建数据权限 @@ -214,24 +210,24 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { @Override @Transactional(rollbackFor = Exception.class) - public List createCustomerBatch(List customerCreateReqBOs, Long userId) { - if (CollUtil.isEmpty(customerCreateReqBOs)) { - return emptyList(); - } + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}", + success = CRM_CUSTOMER_CREATE_SUCCESS) + public Long createCustomer(CrmCustomerCreateReqBO customerCreateReq, Long userId) { + // 1. 插入客户 + CrmCustomerDO customer = BeanUtils.toBean(customerCreateReq, CrmCustomerDO.class).setOwnerUserId(userId) + .setLockStatus(false).setDealStatus(false).setReceiveTime(LocalDateTime.now()); + customerMapper.insert(customer); - // 创建客户 - List customers = convertList(customerCreateReqBOs, customerBO -> - BeanUtils.toBean(customerBO, CrmCustomerDO.class).setOwnerUserId(userId)); - customerMapper.insertBatch(customers); + // 2. 创建数据权限 + permissionService.createPermission(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) + .setBizId(customer.getId()).setUserId(userId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); // 设置当前操作的人为负责人 - // 创建负责人数据权限 - permissionService.createPermissionBatch(convertList(customers, customer -> new CrmPermissionCreateReqBO() - .setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()).setBizId(customer.getId()).setUserId(userId) - .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()))); - return customers; + // 3. 记录操作日志上下文 + LogRecordContext.putVariable("customer", customer); + return customer.getId(); } - // ==================== 公海相关操作 ==================== +// ==================== 公海相关操作 ==================== @Override @Transactional(rollbackFor = Exception.class) diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java index dc2001565f..9a04e2d94d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java @@ -7,7 +7,6 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordPageReqVO; import cn.iocoder.yudao.module.crm.controller.admin.followup.vo.CrmFollowUpRecordSaveReqVO; import cn.iocoder.yudao.module.crm.dal.dataobject.followup.CrmFollowUpRecordDO; -import cn.iocoder.yudao.module.crm.dal.dataobject.permission.CrmPermissionDO; import cn.iocoder.yudao.module.crm.dal.mysql.followup.CrmFollowUpRecordMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; @@ -31,10 +30,10 @@ import java.util.Collections; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.anyMatch; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_DELETE_DENIED; import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.FOLLOW_UP_RECORD_NOT_EXISTS; +import static cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils.hasPermission; /** * 跟进记录 Service 实现类 @@ -94,14 +93,15 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService { customerService.updateCustomerFollowUp(updateFollowUpReqBO); } - // TODO @puhui999:这两个,不更新 contactLastTime、contactLastContent,只更新 nextTime // 3.1 更新 contactIds 对应的记录 if (CollUtil.isNotEmpty(createReqVO.getContactIds())) { - contactService.updateContactFollowUpBatch(convertList(createReqVO.getContactIds(), updateFollowUpReqBO::setBizId)); + contactService.updateContactFollowUpBatch(convertList(createReqVO.getContactIds(), + contactId -> updateFollowUpReqBO.setBizId(contactId).setContactLastTime(null).setContactLastContent(null))); } // 3.2 需要更新 businessIds、contactIds 对应的记录 if (CollUtil.isNotEmpty(createReqVO.getBusinessIds())) { - businessService.updateBusinessFollowUpBatch(convertList(createReqVO.getBusinessIds(), updateFollowUpReqBO::setBizId)); + businessService.updateBusinessFollowUpBatch(convertList(createReqVO.getBusinessIds(), + businessId -> updateFollowUpReqBO.setBizId(businessId).setContactLastTime(null).setContactLastContent(null))); } return followUpRecord.getId(); } @@ -112,8 +112,7 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService { return; } - List followUpRecords = BeanUtils.toBean(followUpCreateReqBOs, CrmFollowUpRecordDO.class); - crmFollowUpRecordMapper.insertBatch(followUpRecords); + crmFollowUpRecordMapper.insertBatch(BeanUtils.toBean(followUpCreateReqBOs, CrmFollowUpRecordDO.class)); } @Override @@ -121,12 +120,7 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService { // 校验存在 CrmFollowUpRecordDO followUpRecord = validateFollowUpRecordExists(id); // 校验权限 - // TODO @puhui999:是不是封装一个 hasPermission,更简介一点; - List permissionList = permissionService.getPermissionListByBiz( - followUpRecord.getBizType(), followUpRecord.getBizId()); - boolean hasPermission = anyMatch(permissionList, permission -> - ObjUtil.equal(permission.getUserId(), userId) && ObjUtil.equal(permission.getLevel(), CrmPermissionLevelEnum.OWNER.getLevel())); - if (!hasPermission) { + if (!hasPermission(followUpRecord.getBizType(), followUpRecord.getBizId(), userId, CrmPermissionLevelEnum.OWNER)) { throw exception(FOLLOW_UP_RECORD_DELETE_DENIED); } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java index 74e7894367..57f1849eb8 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmUpdateFollowUpReqBO.java @@ -21,7 +21,6 @@ public class CrmUpdateFollowUpReqBO { @Schema(description = "最后跟进时间") @DiffLogField(name = "最后跟进时间") - @NotNull(message = "最后跟进时间不能为空") private LocalDateTime contactLastTime; @Schema(description = "下次联系时间") @@ -30,7 +29,6 @@ public class CrmUpdateFollowUpReqBO { @Schema(description = "最后更进内容") @DiffLogField(name = "最后更进内容") - @NotNull(message = "最后更进内容不能为空") private String contactLastContent; } From ed92bf45ce2b157fe309e59c24dfb12bd1cfb72e Mon Sep 17 00:00:00 2001 From: puhui999 Date: Tue, 23 Jan 2024 23:30:13 +0800 Subject: [PATCH 2/2] =?UTF-8?q?CRM:=20=E5=AE=A2=E6=88=B7=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=8E=89=E5=85=A5=E5=85=AC=E6=B5=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yudao-module-crm/yudao-module-crm-biz/pom.xml | 4 ++ .../dal/mysql/customer/CrmCustomerMapper.java | 7 ++ .../customer/CrmCustomerAutoPutPoolJob.java | 27 ++++++++ .../yudao/module/crm/job/package-info.java | 1 + .../service/customer/CrmCustomerService.java | 7 ++ .../customer/CrmCustomerServiceImpl.java | 64 +++++++++++++++---- 6 files changed, 99 insertions(+), 11 deletions(-) create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java diff --git a/yudao-module-crm/yudao-module-crm-biz/pom.xml b/yudao-module-crm/yudao-module-crm-biz/pom.xml index 9e1a9e1521..d786a90fad 100644 --- a/yudao-module-crm/yudao-module-crm-biz/pom.xml +++ b/yudao-module-crm/yudao-module-crm-biz/pom.xml @@ -70,5 +70,9 @@ cn.iocoder.boot yudao-spring-boot-starter-test + + cn.iocoder.boot + yudao-spring-boot-starter-biz-tenant + 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 63e28329dc..c38d114c2b 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 @@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerPageR import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.util.CrmQueryWrapperUtils; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import org.apache.ibatis.annotations.Mapper; import org.springframework.lang.Nullable; @@ -99,4 +100,10 @@ public interface CrmCustomerMapper extends BaseMapperX { return selectJoinPage(pageReqVO, CrmCustomerDO.class, query); } + default List selectListByLockStatusAndOwnerUserIdNotNull(Boolean lockStatus) { + return selectList(new LambdaQueryWrapper() + .eq(CrmCustomerDO::getLockStatus, lockStatus) + .isNotNull(CrmCustomerDO::getOwnerUserId)); + } + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java new file mode 100644 index 0000000000..3d71df4f49 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/customer/CrmCustomerAutoPutPoolJob.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.crm.job.customer; + +import cn.iocoder.yudao.framework.quartz.core.handler.JobHandler; +import cn.iocoder.yudao.framework.tenant.core.job.TenantJob; +import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +/** + * 客户自动掉入公海 Job + * + * @author 芋道源码 + */ +@Component +public class CrmCustomerAutoPutPoolJob implements JobHandler { + + @Resource + private CrmCustomerService customerService; + + @Override + @TenantJob + public String execute(String param) { + int count = customerService.customerAutoPutPoolBySystem(); + return String.format("掉入公海客户 %s 个", count); + } + +} \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java new file mode 100644 index 0000000000..85cccce729 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/job/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.module.crm.job; \ No newline at end of file diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java index d813b2b4d1..e4d06d2fc1 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 @@ -126,4 +126,11 @@ public interface CrmCustomerService { */ void receiveCustomer(List ids, Long ownerUserId, Boolean isReceive); + /** + * 【系统】客户自动掉入公海 + * + * @return 掉入公海数量 + */ + int customerAutoPutPoolBySystem(); + } 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 2958dbdf34..2077359ed8 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 @@ -2,9 +2,11 @@ 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.extra.spring.SpringUtil; 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; @@ -13,6 +15,7 @@ import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.CrmCustomerTrans 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; +import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; @@ -31,6 +34,7 @@ import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; import com.mzt.logapi.starter.annotation.LogRecord; import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -55,6 +59,7 @@ import static java.util.Collections.singletonList; * @author Wanwan */ @Service +@Slf4j @Validated public class CrmCustomerServiceImpl implements CrmCustomerService { @@ -67,6 +72,9 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { private CrmCustomerLimitConfigService customerLimitConfigService; @Resource @Lazy + private CrmCustomerPoolConfigService customerPoolConfigService; + @Resource + @Lazy private CrmContactService contactService; @Resource @Lazy @@ -245,17 +253,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { // 1.3. 校验客户是否锁定 validateCustomerIsLocked(customer, true); - // 2.1 设置负责人为 NULL - int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null); - if (updateOwnerUserIncr == 0) { - throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL); - } - // 2.2 删除负责人数据权限 - permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(), - CrmPermissionLevelEnum.OWNER.getLevel()); - - // 3. 联系人的负责人,也要设置为 null。因为:因为领取后,负责人也要关联过来,这块和 receiveCustomer 是对应的 - contactService.updateOwnerUserIdByCustomerId(customer.getId(), null); + // 2. 客户放入公海 + putCustomerPool(customer); // 记录操作日志上下文 LogRecordContext.putVariable("customerName", customer.getName()); @@ -313,6 +312,49 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { } } + @Override + public int customerAutoPutPoolBySystem() { + CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); + if (poolConfig == null || !poolConfig.getEnabled()) { + return 0; + } + // 获取没有锁定的不在公海的客户 + List customerList = customerMapper.selectListByLockStatusAndOwnerUserIdNotNull(Boolean.FALSE); + List poolCustomerList = CollectionUtils.filterList(customerList, customer -> { + // 1.1 未成交放入公海 + if (!customer.getDealStatus()) { + return (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0; + } + // 1.2 未跟进放入公海 + LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime()); + return (poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime)) <= 0; + }); + int count = 0; + for (CrmCustomerDO customer : poolCustomerList) { + try { + getSelf().putCustomerPool(customer); + count++; + } catch (Throwable e) { + log.error("[customerAutoPutPoolBySystem][Customer 客户({}) 放入公海异常]", customer.getId(), e); + } + } + return count; + } + + private void putCustomerPool(CrmCustomerDO customer) { + // 1. 设置负责人为 NULL + int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null); + if (updateOwnerUserIncr == 0) { + throw exception(CUSTOMER_UPDATE_OWNER_USER_FAIL); + } + // 2. 删除负责人数据权限 + permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), customer.getId(), + CrmPermissionLevelEnum.OWNER.getLevel()); + + // 3. 联系人的负责人,也要设置为 null。因为:因为领取后,负责人也要关联过来,这块和 receiveCustomer 是对应的 + contactService.updateOwnerUserIdByCustomerId(customer.getId(), null); + } + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_RECEIVE_SUB_TYPE, bizNo = "{{#customer.id}}", success = CRM_CUSTOMER_RECEIVE_SUCCESS) public void receiveCustomerLog(CrmCustomerDO customer, String ownerUserName) {