Commit b444312ea888baa950a829c1105445ab463a9eaf
1 parent
3e652493
✨ CRM:优化客户的过期到公海的逻辑
Showing
9 changed files
with
187 additions
and
183 deletions
yudao-module-crm/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/enums/LogRecordConstants.java
| @@ -45,6 +45,8 @@ public interface LogRecordConstants { | @@ -45,6 +45,8 @@ public interface LogRecordConstants { | ||
| 45 | String CRM_CUSTOMER_IMPORT_SUCCESS = "{{#isUpdate ? '导入并更新了客户【'+ #customer.name +'】' : '导入了客户【'+ #customer.name +'】'}}"; | 45 | String CRM_CUSTOMER_IMPORT_SUCCESS = "{{#isUpdate ? '导入并更新了客户【'+ #customer.name +'】' : '导入了客户【'+ #customer.name +'】'}}"; |
| 46 | String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUB_TYPE = "更新客户成交状态"; | 46 | String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUB_TYPE = "更新客户成交状态"; |
| 47 | String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUCCESS = "更新了客户【{{#customerName}}】的成交状态为【{{#dealStatus ? '已成交' : '未成交'}}】"; | 47 | String CRM_CUSTOMER_UPDATE_DEAL_STATUS_SUCCESS = "更新了客户【{{#customerName}}】的成交状态为【{{#dealStatus ? '已成交' : '未成交'}}】"; |
| 48 | + String CRM_CUSTOMER_FOLLOW_UP_SUB_TYPE = "客户跟进"; | ||
| 49 | + String CRM_CUSTOMER_FOLLOW_UP_SUCCESS = "客户跟进【{{#customerName}}】"; | ||
| 48 | 50 | ||
| 49 | // ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 ======================= | 51 | // ======================= CRM_CUSTOMER_LIMIT_CONFIG 客户限制配置 ======================= |
| 50 | 52 |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/clue/CrmClueController.java
| @@ -43,6 +43,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. | @@ -43,6 +43,7 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. | ||
| 43 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | 43 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; |
| 44 | import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; | 44 | import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; |
| 45 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | 45 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 46 | +import static java.util.Collections.singletonList; | ||
| 46 | 47 | ||
| 47 | @Tag(name = "管理后台 - 线索") | 48 | @Tag(name = "管理后台 - 线索") |
| 48 | @RestController | 49 | @RestController |
| @@ -93,11 +94,11 @@ public class CrmClueController { | @@ -93,11 +94,11 @@ public class CrmClueController { | ||
| 93 | return success(buildClueDetail(clue)); | 94 | return success(buildClueDetail(clue)); |
| 94 | } | 95 | } |
| 95 | 96 | ||
| 96 | - public CrmClueRespVO buildClueDetail(CrmClueDO clue) { | 97 | + private CrmClueRespVO buildClueDetail(CrmClueDO clue) { |
| 97 | if (clue == null) { | 98 | if (clue == null) { |
| 98 | return null; | 99 | return null; |
| 99 | } | 100 | } |
| 100 | - return buildClueDetailList(Collections.singletonList(clue)).get(0); | 101 | + return buildClueDetailList(singletonList(clue)).get(0); |
| 101 | } | 102 | } |
| 102 | 103 | ||
| 103 | @GetMapping("/page") | 104 | @GetMapping("/page") |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/customer/CrmCustomerController.java
| @@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer; | @@ -2,9 +2,9 @@ package cn.iocoder.yudao.module.crm.controller.admin.customer; | ||
| 2 | 2 | ||
| 3 | import cn.hutool.core.collection.CollUtil; | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | import cn.hutool.core.map.MapUtil; | 4 | import cn.hutool.core.map.MapUtil; |
| 5 | -import cn.hutool.core.util.ObjUtil; | ||
| 6 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; | 5 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 7 | +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | ||
| 8 | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | 8 | import cn.iocoder.yudao.framework.common.util.collection.MapUtils; |
| 9 | import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; | 9 | import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; |
| 10 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; | 10 | import cn.iocoder.yudao.framework.common.util.number.NumberUtils; |
| @@ -15,7 +15,6 @@ import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; | @@ -15,7 +15,6 @@ import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog; | ||
| 15 | import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; | 15 | import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; |
| 16 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; | 16 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; |
| 17 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; | 17 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; |
| 18 | -import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; | ||
| 19 | import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService; | 18 | import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerPoolConfigService; |
| 20 | import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; | 19 | import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService; |
| 21 | import cn.iocoder.yudao.module.system.api.dept.DeptApi; | 20 | import cn.iocoder.yudao.module.system.api.dept.DeptApi; |
| @@ -40,13 +39,11 @@ import java.util.List; | @@ -40,13 +39,11 @@ import java.util.List; | ||
| 40 | import java.util.Map; | 39 | import java.util.Map; |
| 41 | import java.util.stream.Stream; | 40 | import java.util.stream.Stream; |
| 42 | 41 | ||
| 43 | -import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | ||
| 44 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; | 42 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 45 | import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; | 43 | import static cn.iocoder.yudao.framework.common.pojo.PageParam.PAGE_SIZE_NONE; |
| 46 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; | 44 | import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; |
| 47 | import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; | 45 | import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT; |
| 48 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; | 46 | import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId; |
| 49 | -import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED; | ||
| 50 | import static java.util.Collections.singletonList; | 47 | import static java.util.Collections.singletonList; |
| 51 | 48 | ||
| 52 | @Tag(name = "管理后台 - CRM 客户") | 49 | @Tag(name = "管理后台 - CRM 客户") |
| @@ -123,6 +120,7 @@ public class CrmCustomerController { | @@ -123,6 +120,7 @@ public class CrmCustomerController { | ||
| 123 | @Operation(summary = "获得客户分页") | 120 | @Operation(summary = "获得客户分页") |
| 124 | @PreAuthorize("@ss.hasPermission('crm:customer:query')") | 121 | @PreAuthorize("@ss.hasPermission('crm:customer:query')") |
| 125 | public CommonResult<PageResult<CrmCustomerRespVO>> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) { | 122 | public CommonResult<PageResult<CrmCustomerRespVO>> getCustomerPage(@Valid CrmCustomerPageReqVO pageVO) { |
| 123 | + customerService.autoPutCustomerPool(); | ||
| 126 | // 1. 查询客户分页 | 124 | // 1. 查询客户分页 |
| 127 | PageResult<CrmCustomerDO> pageResult = customerService.getCustomerPage(pageVO, getLoginUserId()); | 125 | PageResult<CrmCustomerDO> pageResult = customerService.getCustomerPage(pageVO, getLoginUserId()); |
| 128 | if (CollUtil.isEmpty(pageResult.getList())) { | 126 | if (CollUtil.isEmpty(pageResult.getList())) { |
| @@ -159,45 +157,21 @@ public class CrmCustomerController { | @@ -159,45 +157,21 @@ public class CrmCustomerController { | ||
| 159 | }); | 157 | }); |
| 160 | } | 158 | } |
| 161 | 159 | ||
| 162 | - // TODO @芋艿:需要 review 下 | ||
| 163 | - @GetMapping("/put-in-pool-remind-page") | 160 | + @GetMapping("/put-pool-remind-page") |
| 164 | @Operation(summary = "获得待进入公海客户分页") | 161 | @Operation(summary = "获得待进入公海客户分页") |
| 165 | @PreAuthorize("@ss.hasPermission('crm:customer:query')") | 162 | @PreAuthorize("@ss.hasPermission('crm:customer:query')") |
| 166 | - public CommonResult<PageResult<CrmCustomerRespVO>> getPutInPoolRemindCustomerPage(@Valid CrmCustomerPageReqVO pageVO) { | ||
| 167 | - // 获取公海配置 TODO @dbh52:合并到 getPutInPoolRemindCustomerPage 会更合适哈; | ||
| 168 | - CrmCustomerPoolConfigDO poolConfigDO = customerPoolConfigService.getCustomerPoolConfig(); | ||
| 169 | - if (ObjUtil.isNull(poolConfigDO) | ||
| 170 | - || Boolean.FALSE.equals(poolConfigDO.getEnabled()) | ||
| 171 | - || Boolean.FALSE.equals(poolConfigDO.getNotifyEnabled())) { | ||
| 172 | - throw exception(CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED); | ||
| 173 | - } | ||
| 174 | - | 163 | + public CommonResult<PageResult<CrmCustomerRespVO>> getPutPoolRemindCustomerPage(@Valid CrmCustomerPageReqVO pageVO) { |
| 175 | // 1. 查询客户分页 | 164 | // 1. 查询客户分页 |
| 176 | - PageResult<CrmCustomerDO> pageResult = customerService.getPutInPoolRemindCustomerPage(pageVO, poolConfigDO, getLoginUserId()); | ||
| 177 | - if (CollUtil.isEmpty(pageResult.getList())) { | ||
| 178 | - return success(PageResult.empty(pageResult.getTotal())); | ||
| 179 | - } | ||
| 180 | - | 165 | + PageResult<CrmCustomerDO> pageResult = customerService.getPutPoolRemindCustomerPage(pageVO, getLoginUserId()); |
| 181 | // 2. 拼接数据 | 166 | // 2. 拼接数据 |
| 182 | return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal())); | 167 | return success(new PageResult<>(buildCustomerDetailList(pageResult.getList()), pageResult.getTotal())); |
| 183 | } | 168 | } |
| 184 | 169 | ||
| 185 | - @GetMapping("/put-in-pool-remind-count") | 170 | + @GetMapping("/put-pool-remind-count") |
| 186 | @Operation(summary = "获得待进入公海客户数量") | 171 | @Operation(summary = "获得待进入公海客户数量") |
| 187 | @PreAuthorize("@ss.hasPermission('crm:customer:query')") | 172 | @PreAuthorize("@ss.hasPermission('crm:customer:query')") |
| 188 | - public CommonResult<Long> getPutInPoolRemindCustomerCount() { | ||
| 189 | - // 获取公海配置 | ||
| 190 | - CrmCustomerPoolConfigDO poolConfigDO = customerPoolConfigService.getCustomerPoolConfig(); | ||
| 191 | - if (ObjUtil.isNull(poolConfigDO) | ||
| 192 | - || Boolean.FALSE.equals(poolConfigDO.getEnabled()) | ||
| 193 | - || Boolean.FALSE.equals(poolConfigDO.getNotifyEnabled())) { | ||
| 194 | - throw exception(CUSTOMER_POOL_CONFIG_NOT_EXISTS_OR_DISABLED); | ||
| 195 | - } | ||
| 196 | - CrmCustomerPageReqVO pageVO = new CrmCustomerPageReqVO() | ||
| 197 | - .setPool(null) | ||
| 198 | - .setContactStatus(CrmCustomerPageReqVO.CONTACT_TODAY) | ||
| 199 | - .setSceneType(CrmSceneTypeEnum.OWNER.getType()); | ||
| 200 | - return success(customerService.getPutInPoolRemindCustomerCount(pageVO, poolConfigDO, getLoginUserId())); | 173 | + public CommonResult<Long> getPutPoolRemindCustomerCount() { |
| 174 | + return success(customerService.getPutPoolRemindCustomerCount(getLoginUserId())); | ||
| 201 | } | 175 | } |
| 202 | 176 | ||
| 203 | @GetMapping("/today-customer-count") | 177 | @GetMapping("/today-customer-count") |
| @@ -225,24 +199,26 @@ public class CrmCustomerController { | @@ -225,24 +199,26 @@ public class CrmCustomerController { | ||
| 225 | if (poolConfig == null || !poolConfig.getEnabled()) { | 199 | if (poolConfig == null || !poolConfig.getEnabled()) { |
| 226 | return MapUtil.empty(); | 200 | return MapUtil.empty(); |
| 227 | } | 201 | } |
| 202 | + list = CollectionUtils.filterList(list, customer -> { | ||
| 203 | + // 特殊:如果没负责人,则说明已经在公海,不用计算 | ||
| 204 | + if (customer.getOwnerUserId() == null) { | ||
| 205 | + return false; | ||
| 206 | + } | ||
| 207 | + // 已成交 or 已锁定,不进入公海 | ||
| 208 | + return !customer.getDealStatus() && !customer.getLockStatus(); | ||
| 209 | + }); | ||
| 228 | return convertMap(list, CrmCustomerDO::getId, customer -> { | 210 | return convertMap(list, CrmCustomerDO::getId, customer -> { |
| 229 | - // TODO 芋艿:这样计算,貌似有点问题 | ||
| 230 | // 1.1 未成交放入公海天数 | 211 | // 1.1 未成交放入公海天数 |
| 231 | - long dealExpireDay = 0; | ||
| 232 | - if (!customer.getDealStatus()) { | ||
| 233 | - dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime()); | ||
| 234 | - } | ||
| 235 | - if (dealExpireDay < 0) { | ||
| 236 | - dealExpireDay = 0; | ||
| 237 | - } | 212 | + long dealExpireDay = poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getOwnerTime()); |
| 238 | // 1.2 未跟进放入公海天数 | 213 | // 1.2 未跟进放入公海天数 |
| 239 | - LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime()); | ||
| 240 | - long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime); | ||
| 241 | - if (contactExpireDay < 0) { | ||
| 242 | - contactExpireDay = 0; | 214 | + LocalDateTime lastTime = customer.getOwnerTime(); |
| 215 | + if (customer.getContactLastTime() != null && customer.getContactLastTime().isAfter(lastTime)) { | ||
| 216 | + lastTime = customer.getContactLastTime(); | ||
| 243 | } | 217 | } |
| 218 | + long contactExpireDay = poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime); | ||
| 244 | // 2. 返回最小的天数 | 219 | // 2. 返回最小的天数 |
| 245 | - return Math.min(dealExpireDay, contactExpireDay); | 220 | + long poolDay = Math.min(dealExpireDay, contactExpireDay); |
| 221 | + return poolDay > 0 ? poolDay : 0; | ||
| 246 | }); | 222 | }); |
| 247 | } | 223 | } |
| 248 | 224 |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/customer/CrmCustomerDO.java
| @@ -57,6 +57,10 @@ public class CrmCustomerDO extends BaseDO { | @@ -57,6 +57,10 @@ public class CrmCustomerDO extends BaseDO { | ||
| 57 | * 关联 AdminUserDO 的 id 字段 | 57 | * 关联 AdminUserDO 的 id 字段 |
| 58 | */ | 58 | */ |
| 59 | private Long ownerUserId; | 59 | private Long ownerUserId; |
| 60 | + /** | ||
| 61 | + * 成为负责人的时间 | ||
| 62 | + */ | ||
| 63 | + private LocalDateTime ownerTime; | ||
| 60 | 64 | ||
| 61 | /** | 65 | /** |
| 62 | * 锁定状态 | 66 | * 锁定状态 |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/customer/CrmCustomerMapper.java
| @@ -31,40 +31,6 @@ import java.util.List; | @@ -31,40 +31,6 @@ import java.util.List; | ||
| 31 | @Mapper | 31 | @Mapper |
| 32 | public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> { | 32 | public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> { |
| 33 | 33 | ||
| 34 | - private static MPJLambdaWrapperX<CrmCustomerDO> buildPutInPoolRemindCustomerWrapper(CrmCustomerPageReqVO pageReqVO, CrmCustomerPoolConfigDO poolConfigDO, Long userId) { | ||
| 35 | - MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>(); | ||
| 36 | - // 拼接数据权限的查询条件 | ||
| 37 | - CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), | ||
| 38 | - CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), null); | ||
| 39 | - | ||
| 40 | - // 锁定状态不需要提醒 | ||
| 41 | - query.ne(CrmCustomerDO::getLockStatus, true); | ||
| 42 | - | ||
| 43 | - // 情况一:未成交提醒日期区间 | ||
| 44 | - Integer dealExpireDays = poolConfigDO.getDealExpireDays(); | ||
| 45 | - LocalDateTime startDealRemindDate = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()) | ||
| 46 | - .minusDays(dealExpireDays); | ||
| 47 | - LocalDateTime endDealRemindDate = LocalDateTimeUtil.endOfDay(LocalDateTime.now()) | ||
| 48 | - .minusDays(Math.max(dealExpireDays - poolConfigDO.getNotifyDays(), 0)); | ||
| 49 | - // 情况二:未跟进提醒日期区间 | ||
| 50 | - Integer contactExpireDays = poolConfigDO.getContactExpireDays(); | ||
| 51 | - LocalDateTime startContactRemindDate = LocalDateTimeUtil.beginOfDay(LocalDateTime.now()) | ||
| 52 | - .minusDays(contactExpireDays); | ||
| 53 | - LocalDateTime endContactRemindDate = LocalDateTimeUtil.endOfDay(LocalDateTime.now()) | ||
| 54 | - .minusDays(Math.max(contactExpireDays - poolConfigDO.getNotifyDays(), 0)); | ||
| 55 | - query | ||
| 56 | - // 情况一:1. 未成交放入公海提醒 | ||
| 57 | - .eq(CrmCustomerDO::getDealStatus, false) | ||
| 58 | - .between(CrmCustomerDO::getCreateTime, startDealRemindDate, endDealRemindDate) | ||
| 59 | - // 情况二:未跟进放入公海提醒 | ||
| 60 | - .or() // 2.1 contactLastTime 为空 TODO 芋艿:这个要不要搞个默认值; | ||
| 61 | - .isNull(CrmCustomerDO::getContactLastTime) | ||
| 62 | - .between(CrmCustomerDO::getCreateTime, startContactRemindDate, endContactRemindDate) | ||
| 63 | - .or() // 2.2 ContactLastTime 不为空 | ||
| 64 | - .between(CrmCustomerDO::getContactLastTime, startContactRemindDate, endContactRemindDate); | ||
| 65 | - return query; | ||
| 66 | - } | ||
| 67 | - | ||
| 68 | default Long selectCountByLockStatusAndOwnerUserId(Boolean lockStatus, Long ownerUserId) { | 34 | default Long selectCountByLockStatusAndOwnerUserId(Boolean lockStatus, Long ownerUserId) { |
| 69 | return selectCount(new LambdaUpdateWrapper<CrmCustomerDO>() | 35 | return selectCount(new LambdaUpdateWrapper<CrmCustomerDO>() |
| 70 | .eq(CrmCustomerDO::getLockStatus, lockStatus) | 36 | .eq(CrmCustomerDO::getLockStatus, lockStatus) |
| @@ -124,30 +90,80 @@ public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> { | @@ -124,30 +90,80 @@ public interface CrmCustomerMapper extends BaseMapperX<CrmCustomerDO> { | ||
| 124 | return selectJoinList(CrmCustomerDO.class, query); | 90 | return selectJoinList(CrmCustomerDO.class, query); |
| 125 | } | 91 | } |
| 126 | 92 | ||
| 127 | - default List<CrmCustomerDO> selectListByLockAndNotPool(Boolean lockStatus) { | ||
| 128 | - return selectList(new LambdaQueryWrapper<CrmCustomerDO>() | ||
| 129 | - .eq(CrmCustomerDO::getLockStatus, lockStatus) | ||
| 130 | - .gt(CrmCustomerDO::getOwnerUserId, 0)); | ||
| 131 | - } | ||
| 132 | - | ||
| 133 | default CrmCustomerDO selectByCustomerName(String name) { | 93 | default CrmCustomerDO selectByCustomerName(String name) { |
| 134 | return selectOne(CrmCustomerDO::getName, name); | 94 | return selectOne(CrmCustomerDO::getName, name); |
| 135 | } | 95 | } |
| 136 | 96 | ||
| 137 | - default PageResult<CrmCustomerDO> selectPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO, | ||
| 138 | - CrmCustomerPoolConfigDO poolConfigDO, | ||
| 139 | - Long userId) { | ||
| 140 | - final MPJLambdaWrapperX<CrmCustomerDO> query = buildPutInPoolRemindCustomerWrapper(pageReqVO, poolConfigDO, userId); | 97 | + default PageResult<CrmCustomerDO> selectPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO, |
| 98 | + CrmCustomerPoolConfigDO poolConfig, | ||
| 99 | + Long userId) { | ||
| 100 | + final MPJLambdaWrapperX<CrmCustomerDO> query = buildPutPoolRemindCustomerQuery(pageReqVO, poolConfig, userId); | ||
| 141 | return selectJoinPage(pageReqVO, CrmCustomerDO.class, query.selectAll(CrmCustomerDO.class)); | 101 | return selectJoinPage(pageReqVO, CrmCustomerDO.class, query.selectAll(CrmCustomerDO.class)); |
| 142 | } | 102 | } |
| 143 | 103 | ||
| 144 | - default Long selectPutInPoolRemindCustomerCount(CrmCustomerPageReqVO pageReqVO, | ||
| 145 | - CrmCustomerPoolConfigDO poolConfigDO, | ||
| 146 | - Long userId) { | ||
| 147 | - final MPJLambdaWrapperX<CrmCustomerDO> query = buildPutInPoolRemindCustomerWrapper(pageReqVO, poolConfigDO, userId); | 104 | + default Long selectPutPoolRemindCustomerCount(CrmCustomerPageReqVO pageReqVO, |
| 105 | + CrmCustomerPoolConfigDO poolConfigDO, | ||
| 106 | + Long userId) { | ||
| 107 | + final MPJLambdaWrapperX<CrmCustomerDO> query = buildPutPoolRemindCustomerQuery(pageReqVO, poolConfigDO, userId); | ||
| 148 | return selectCount(query); | 108 | return selectCount(query); |
| 149 | } | 109 | } |
| 150 | 110 | ||
| 111 | + private static MPJLambdaWrapperX<CrmCustomerDO> buildPutPoolRemindCustomerQuery(CrmCustomerPageReqVO pageReqVO, | ||
| 112 | + CrmCustomerPoolConfigDO poolConfig, | ||
| 113 | + Long userId) { | ||
| 114 | + MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>(); | ||
| 115 | + // 拼接数据权限的查询条件 | ||
| 116 | + CrmQueryWrapperUtils.appendPermissionCondition(query, CrmBizTypeEnum.CRM_CUSTOMER.getType(), | ||
| 117 | + CrmCustomerDO::getId, userId, pageReqVO.getSceneType(), null); | ||
| 118 | + | ||
| 119 | + // 未锁定 + 未成交 | ||
| 120 | + query.eq(CrmCustomerDO::getLockStatus, false).eq(CrmCustomerDO::getDealStatus, false); | ||
| 121 | + | ||
| 122 | + // 情况一:未成交提醒日期区间 | ||
| 123 | + Integer dealExpireDays = poolConfig.getDealExpireDays(); | ||
| 124 | + LocalDateTime startDealRemindTime = LocalDateTime.now().minusDays(dealExpireDays); | ||
| 125 | + LocalDateTime endDealRemindTime = LocalDateTime.now() | ||
| 126 | + .minusDays(Math.max(dealExpireDays - poolConfig.getNotifyDays(), 0)); | ||
| 127 | + // 情况二:未跟进提醒日期区间 | ||
| 128 | + Integer contactExpireDays = poolConfig.getContactExpireDays(); | ||
| 129 | + LocalDateTime startContactRemindTime = LocalDateTime.now().minusDays(contactExpireDays); | ||
| 130 | + LocalDateTime endContactRemindTime = LocalDateTime.now() | ||
| 131 | + .minusDays(Math.max(contactExpireDays - poolConfig.getNotifyDays(), 0)); | ||
| 132 | + query.and(q -> { | ||
| 133 | + // 情况一:成交超时提醒 | ||
| 134 | + q.between(CrmCustomerDO::getOwnerTime, startDealRemindTime, endDealRemindTime) | ||
| 135 | + // 情况二:跟进超时提醒 | ||
| 136 | + .or(w -> w.between(CrmCustomerDO::getOwnerTime, startContactRemindTime, endContactRemindTime) | ||
| 137 | + .and(p -> p.between(CrmCustomerDO::getContactLastTime, startContactRemindTime, endContactRemindTime) | ||
| 138 | + .or().isNull(CrmCustomerDO::getContactLastTime))); | ||
| 139 | + }); | ||
| 140 | + return query; | ||
| 141 | + } | ||
| 142 | + | ||
| 143 | + /** | ||
| 144 | + * 获得需要过期到公海的客户列表 | ||
| 145 | + * | ||
| 146 | + * @return 客户列表 | ||
| 147 | + */ | ||
| 148 | + default List<CrmCustomerDO> selectListByAutoPool(CrmCustomerPoolConfigDO poolConfig) { | ||
| 149 | + LambdaQueryWrapper<CrmCustomerDO> query = new LambdaQueryWrapper<>(); | ||
| 150 | + query.gt(CrmCustomerDO::getOwnerUserId, 0); | ||
| 151 | + // 未锁定 + 未成交 | ||
| 152 | + query.eq(CrmCustomerDO::getLockStatus, false).eq(CrmCustomerDO::getDealStatus, false); | ||
| 153 | + // 已经超时 | ||
| 154 | + LocalDateTime dealExpireTime = LocalDateTime.now().minusDays(poolConfig.getDealExpireDays()); | ||
| 155 | + LocalDateTime contactExpireTime = LocalDateTime.now().minusDays(poolConfig.getContactExpireDays()); | ||
| 156 | + query.and(q -> { | ||
| 157 | + // 情况一:成交超时 | ||
| 158 | + q.lt(CrmCustomerDO::getOwnerTime, dealExpireTime) | ||
| 159 | + // 情况二:跟进超时 | ||
| 160 | + .or(w -> w.lt(CrmCustomerDO::getOwnerTime, contactExpireTime) | ||
| 161 | + .and(p -> p.lt(CrmCustomerDO::getContactLastTime, contactExpireTime) | ||
| 162 | + .or().isNull(CrmCustomerDO::getContactLastTime))); | ||
| 163 | + }); | ||
| 164 | + return selectList(query); | ||
| 165 | + } | ||
| 166 | + | ||
| 151 | default Long selectTodayCustomerCount(Long userId) { | 167 | default Long selectTodayCustomerCount(Long userId) { |
| 152 | MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>(); | 168 | MPJLambdaWrapperX<CrmCustomerDO> query = new MPJLambdaWrapperX<>(); |
| 153 | // 我负责的 + 非公海 | 169 | // 我负责的 + 非公海 |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/clue/CrmClueServiceImpl.java
| @@ -78,8 +78,7 @@ public class CrmClueServiceImpl implements CrmClueService { | @@ -78,8 +78,7 @@ public class CrmClueServiceImpl implements CrmClueService { | ||
| 78 | adminUserApi.validateUser(createReqVO.getOwnerUserId()); | 78 | adminUserApi.validateUser(createReqVO.getOwnerUserId()); |
| 79 | 79 | ||
| 80 | // 2. 插入线索 | 80 | // 2. 插入线索 |
| 81 | - CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class) | ||
| 82 | - .setContactLastTime(LocalDateTime.now()); | 81 | + CrmClueDO clue = BeanUtils.toBean(createReqVO, CrmClueDO.class); |
| 83 | clueMapper.insert(clue); | 82 | clueMapper.insert(clue); |
| 84 | 83 | ||
| 85 | // 3. 创建数据权限 | 84 | // 3. 创建数据权限 |
| @@ -129,7 +128,7 @@ public class CrmClueServiceImpl implements CrmClueService { | @@ -129,7 +128,7 @@ public class CrmClueServiceImpl implements CrmClueService { | ||
| 129 | // 校验线索是否存在 | 128 | // 校验线索是否存在 |
| 130 | CrmClueDO oldClue = validateClueExists(id); | 129 | CrmClueDO oldClue = validateClueExists(id); |
| 131 | 130 | ||
| 132 | - // 更新 | 131 | + // 更新线索 |
| 133 | clueMapper.updateById(new CrmClueDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime) | 132 | clueMapper.updateById(new CrmClueDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime) |
| 134 | .setContactLastTime(LocalDateTime.now()).setContactLastContent(contactLastContent)); | 133 | .setContactLastTime(LocalDateTime.now()).setContactLastContent(contactLastContent)); |
| 135 | 134 |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerService.java
| @@ -3,11 +3,10 @@ package cn.iocoder.yudao.module.crm.service.customer; | @@ -3,11 +3,10 @@ package cn.iocoder.yudao.module.crm.service.customer; | ||
| 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; | 4 | import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; |
| 5 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; | 5 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; |
| 6 | -import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; | ||
| 7 | import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; | 6 | import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; |
| 8 | -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; | ||
| 9 | import jakarta.validation.Valid; | 7 | import jakarta.validation.Valid; |
| 10 | 8 | ||
| 9 | +import java.time.LocalDateTime; | ||
| 11 | import java.util.Collection; | 10 | import java.util.Collection; |
| 12 | import java.util.List; | 11 | import java.util.List; |
| 13 | import java.util.Map; | 12 | import java.util.Map; |
| @@ -46,6 +45,15 @@ public interface CrmCustomerService { | @@ -46,6 +45,15 @@ public interface CrmCustomerService { | ||
| 46 | void updateCustomerDealStatus(Long id, Boolean dealStatus); | 45 | void updateCustomerDealStatus(Long id, Boolean dealStatus); |
| 47 | 46 | ||
| 48 | /** | 47 | /** |
| 48 | + * 更新客户相关的跟进信息 | ||
| 49 | + * | ||
| 50 | + * @param id 编号 | ||
| 51 | + * @param contactNextTime 下次联系时间 | ||
| 52 | + * @param contactLastContent 最后联系内容 | ||
| 53 | + */ | ||
| 54 | + void updateCustomerFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent); | ||
| 55 | + | ||
| 56 | + /** | ||
| 49 | * 删除客户 | 57 | * 删除客户 |
| 50 | * | 58 | * |
| 51 | * @param id 编号 | 59 | * @param id 编号 |
| @@ -89,6 +97,23 @@ public interface CrmCustomerService { | @@ -89,6 +97,23 @@ public interface CrmCustomerService { | ||
| 89 | PageResult<CrmCustomerDO> getCustomerPage(CrmCustomerPageReqVO pageReqVO, Long userId); | 97 | PageResult<CrmCustomerDO> getCustomerPage(CrmCustomerPageReqVO pageReqVO, Long userId); |
| 90 | 98 | ||
| 91 | /** | 99 | /** |
| 100 | + * 获得放入公海提醒的客户分页 | ||
| 101 | + * | ||
| 102 | + * @param pageVO 分页查询 | ||
| 103 | + * @param userId 用户编号 | ||
| 104 | + * @return 客户分页 | ||
| 105 | + */ | ||
| 106 | + PageResult<CrmCustomerDO> getPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, Long userId); | ||
| 107 | + | ||
| 108 | + /** | ||
| 109 | + * 获得待进入公海的客户数量 | ||
| 110 | + * | ||
| 111 | + * @param userId 用户编号 | ||
| 112 | + * @return 提醒数量 | ||
| 113 | + */ | ||
| 114 | + Long getPutPoolRemindCustomerCount(Long userId); | ||
| 115 | + | ||
| 116 | + /** | ||
| 92 | * 校验客户是否存在 | 117 | * 校验客户是否存在 |
| 93 | * | 118 | * |
| 94 | * @param customerId 客户 id | 119 | * @param customerId 客户 id |
| @@ -112,13 +137,6 @@ public interface CrmCustomerService { | @@ -112,13 +137,6 @@ public interface CrmCustomerService { | ||
| 112 | void lockCustomer(@Valid CrmCustomerLockReqVO lockReqVO, Long userId); | 137 | void lockCustomer(@Valid CrmCustomerLockReqVO lockReqVO, Long userId); |
| 113 | 138 | ||
| 114 | /** | 139 | /** |
| 115 | - * 更新客户相关更进信息 | ||
| 116 | - * | ||
| 117 | - * @param customerUpdateFollowUpReqBO 请求 | ||
| 118 | - */ | ||
| 119 | - void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO); | ||
| 120 | - | ||
| 121 | - /** | ||
| 122 | * 创建客户 | 140 | * 创建客户 |
| 123 | * | 141 | * |
| 124 | * @param customerCreateReq 请求信息 | 142 | * @param customerCreateReq 请求信息 |
| @@ -162,18 +180,6 @@ public interface CrmCustomerService { | @@ -162,18 +180,6 @@ public interface CrmCustomerService { | ||
| 162 | int autoPutCustomerPool(); | 180 | int autoPutCustomerPool(); |
| 163 | 181 | ||
| 164 | /** | 182 | /** |
| 165 | - * 获得放入公海提醒的客户分页数据 | ||
| 166 | - * | ||
| 167 | - * @param pageVO 分页查询 | ||
| 168 | - * @param poolConfigDO 公海配置 | ||
| 169 | - * @param userId 用户编号 | ||
| 170 | - * @return 客户分页 | ||
| 171 | - */ | ||
| 172 | - PageResult<CrmCustomerDO> getPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, | ||
| 173 | - CrmCustomerPoolConfigDO poolConfigDO, | ||
| 174 | - Long userId); | ||
| 175 | - | ||
| 176 | - /** | ||
| 177 | * 获得今日需联系客户数量 | 183 | * 获得今日需联系客户数量 |
| 178 | * | 184 | * |
| 179 | * @param userId 用户编号 | 185 | * @param userId 用户编号 |
| @@ -182,18 +188,6 @@ public interface CrmCustomerService { | @@ -182,18 +188,6 @@ public interface CrmCustomerService { | ||
| 182 | Long getTodayCustomerCount(Long userId); | 188 | Long getTodayCustomerCount(Long userId); |
| 183 | 189 | ||
| 184 | /** | 190 | /** |
| 185 | - * 获得待进入公海的客户数量 | ||
| 186 | - * | ||
| 187 | - * @param pageVO 分页查询 | ||
| 188 | - * @param poolConfigDO 公海配置 | ||
| 189 | - * @param userId 用户编号 | ||
| 190 | - * @return 提醒数量 | ||
| 191 | - */ | ||
| 192 | - Long getPutInPoolRemindCustomerCount(CrmCustomerPageReqVO pageVO, | ||
| 193 | - CrmCustomerPoolConfigDO poolConfigDO, | ||
| 194 | - Long userId); | ||
| 195 | - | ||
| 196 | - /** | ||
| 197 | * 获得分配给我的客户数量 | 191 | * 获得分配给我的客户数量 |
| 198 | * | 192 | * |
| 199 | * @param userId 用户编号 | 193 | * @param userId 用户编号 |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/customer/CrmCustomerServiceImpl.java
| @@ -8,7 +8,6 @@ import cn.hutool.extra.spring.SpringUtil; | @@ -8,7 +8,6 @@ import cn.hutool.extra.spring.SpringUtil; | ||
| 8 | import cn.iocoder.yudao.framework.common.exception.ServiceException; | 8 | import cn.iocoder.yudao.framework.common.exception.ServiceException; |
| 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; | 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; | 10 | import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; |
| 11 | -import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; | ||
| 12 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | 11 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 13 | import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; | 12 | import cn.iocoder.yudao.module.crm.controller.admin.customer.vo.customer.*; |
| 14 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; | 13 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerDO; |
| @@ -16,6 +15,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfi | @@ -16,6 +15,7 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerLimitConfi | ||
| 16 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; | 15 | import cn.iocoder.yudao.module.crm.dal.dataobject.customer.CrmCustomerPoolConfigDO; |
| 17 | import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; | 16 | import cn.iocoder.yudao.module.crm.dal.mysql.customer.CrmCustomerMapper; |
| 18 | import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; | 17 | import cn.iocoder.yudao.module.crm.enums.common.CrmBizTypeEnum; |
| 18 | +import cn.iocoder.yudao.module.crm.enums.common.CrmSceneTypeEnum; | ||
| 19 | import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; | 19 | import cn.iocoder.yudao.module.crm.enums.permission.CrmPermissionLevelEnum; |
| 20 | import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; | 20 | import cn.iocoder.yudao.module.crm.framework.permission.core.annotations.CrmPermission; |
| 21 | import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; | 21 | import cn.iocoder.yudao.module.crm.framework.permission.core.util.CrmPermissionUtils; |
| @@ -23,7 +23,6 @@ import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; | @@ -23,7 +23,6 @@ import cn.iocoder.yudao.module.crm.service.business.CrmBusinessService; | ||
| 23 | import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; | 23 | import cn.iocoder.yudao.module.crm.service.contact.CrmContactService; |
| 24 | import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; | 24 | import cn.iocoder.yudao.module.crm.service.contract.CrmContractService; |
| 25 | import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; | 25 | import cn.iocoder.yudao.module.crm.service.customer.bo.CrmCustomerCreateReqBO; |
| 26 | -import cn.iocoder.yudao.module.crm.service.followup.bo.CrmUpdateFollowUpReqBO; | ||
| 27 | import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; | 26 | import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService; |
| 28 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; | 27 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO; |
| 29 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; | 28 | import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionTransferReqBO; |
| @@ -43,7 +42,6 @@ import java.time.LocalDateTime; | @@ -43,7 +42,6 @@ import java.time.LocalDateTime; | ||
| 43 | import java.util.*; | 42 | import java.util.*; |
| 44 | 43 | ||
| 45 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 44 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 46 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.filterList; | ||
| 47 | import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; | 45 | import static cn.iocoder.yudao.module.crm.enums.ErrorCodeConstants.*; |
| 48 | import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; | 46 | import static cn.iocoder.yudao.module.crm.enums.LogRecordConstants.*; |
| 49 | import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT; | 47 | import static cn.iocoder.yudao.module.crm.enums.customer.CrmCustomerLimitConfigTypeEnum.CUSTOMER_LOCK_LIMIT; |
| @@ -114,7 +112,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | @@ -114,7 +112,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | ||
| 114 | */ | 112 | */ |
| 115 | private static CrmCustomerDO initCustomer(Object customer, Long ownerUserId) { | 113 | private static CrmCustomerDO initCustomer(Object customer, Long ownerUserId) { |
| 116 | return BeanUtils.toBean(customer, CrmCustomerDO.class).setOwnerUserId(ownerUserId) | 114 | return BeanUtils.toBean(customer, CrmCustomerDO.class).setOwnerUserId(ownerUserId) |
| 117 | - .setLockStatus(false).setDealStatus(false).setContactLastTime(LocalDateTime.now()); | 115 | + .setOwnerTime(LocalDateTime.now()); |
| 118 | } | 116 | } |
| 119 | 117 | ||
| 120 | @Override | 118 | @Override |
| @@ -158,6 +156,22 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | @@ -158,6 +156,22 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | ||
| 158 | } | 156 | } |
| 159 | 157 | ||
| 160 | @Override | 158 | @Override |
| 159 | + @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_FOLLOW_UP_SUB_TYPE, bizNo = "{{#id}", | ||
| 160 | + success = CRM_CUSTOMER_FOLLOW_UP_SUCCESS) | ||
| 161 | + @CrmPermission(bizType = CrmBizTypeEnum.CRM_CUSTOMER, bizId = "#id", level = CrmPermissionLevelEnum.WRITE) | ||
| 162 | + public void updateCustomerFollowUp(Long id, LocalDateTime contactNextTime, String contactLastContent) { | ||
| 163 | + // 1.1 校验存在 | ||
| 164 | + CrmCustomerDO customer = validateCustomerExists(id); | ||
| 165 | + | ||
| 166 | + // 2. 更新客户的跟进信息 | ||
| 167 | + customerMapper.updateById(new CrmCustomerDO().setId(id).setFollowUpStatus(true).setContactNextTime(contactNextTime) | ||
| 168 | + .setContactLastTime(LocalDateTime.now())); | ||
| 169 | + | ||
| 170 | + // 3. 记录操作日志上下文 | ||
| 171 | + LogRecordContext.putVariable("customerName", customer.getName()); | ||
| 172 | + } | ||
| 173 | + | ||
| 174 | + @Override | ||
| 161 | @Transactional(rollbackFor = Exception.class) | 175 | @Transactional(rollbackFor = Exception.class) |
| 162 | @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_DELETE_SUB_TYPE, bizNo = "{{#id}}", | 176 | @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_DELETE_SUB_TYPE, bizNo = "{{#id}}", |
| 163 | success = CRM_CUSTOMER_DELETE_SUCCESS) | 177 | success = CRM_CUSTOMER_DELETE_SUCCESS) |
| @@ -168,7 +182,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | @@ -168,7 +182,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | ||
| 168 | // 1.2 检查引用 | 182 | // 1.2 检查引用 |
| 169 | validateCustomerReference(id); | 183 | validateCustomerReference(id); |
| 170 | 184 | ||
| 171 | - // 2. 删除 | 185 | + // 2. 删除客户 |
| 172 | customerMapper.deleteById(id); | 186 | customerMapper.deleteById(id); |
| 173 | // 3. 删除数据权限 | 187 | // 3. 删除数据权限 |
| 174 | permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); | 188 | permissionService.deletePermission(CrmBizTypeEnum.CRM_CUSTOMER.getType(), id); |
| @@ -209,7 +223,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | @@ -209,7 +223,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | ||
| 209 | permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(), | 223 | permissionService.transferPermission(new CrmPermissionTransferReqBO(userId, CrmBizTypeEnum.CRM_CUSTOMER.getType(), |
| 210 | reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel())); | 224 | reqVO.getId(), reqVO.getNewOwnerUserId(), reqVO.getOldOwnerPermissionLevel())); |
| 211 | // 2.2 转移后重新设置负责人 | 225 | // 2.2 转移后重新设置负责人 |
| 212 | - customerMapper.updateOwnerUserIdById(reqVO.getId(), reqVO.getNewOwnerUserId()); | 226 | + customerMapper.updateById(new CrmCustomerDO().setId(reqVO.getId()) |
| 227 | + .setOwnerUserId(reqVO.getNewOwnerUserId()).setOwnerTime(LocalDateTime.now())); | ||
| 213 | 228 | ||
| 214 | // 3. 记录转移日志 | 229 | // 3. 记录转移日志 |
| 215 | LogRecordContext.putVariable("customer", customer); | 230 | LogRecordContext.putVariable("customer", customer); |
| @@ -226,7 +241,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | @@ -226,7 +241,7 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | ||
| 226 | if (customer.getLockStatus().equals(lockReqVO.getLockStatus())) { | 241 | if (customer.getLockStatus().equals(lockReqVO.getLockStatus())) { |
| 227 | throw exception(customer.getLockStatus() ? CUSTOMER_LOCK_FAIL_IS_LOCK : CUSTOMER_UNLOCK_FAIL_IS_UNLOCK); | 242 | throw exception(customer.getLockStatus() ? CUSTOMER_LOCK_FAIL_IS_LOCK : CUSTOMER_UNLOCK_FAIL_IS_UNLOCK); |
| 228 | } | 243 | } |
| 229 | - // 1.3 校验锁定上限。 | 244 | + // 1.3 校验锁定上限 |
| 230 | if (lockReqVO.getLockStatus()) { | 245 | if (lockReqVO.getLockStatus()) { |
| 231 | validateCustomerExceedLockLimit(userId); | 246 | validateCustomerExceedLockLimit(userId); |
| 232 | } | 247 | } |
| @@ -240,11 +255,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | @@ -240,11 +255,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | ||
| 240 | } | 255 | } |
| 241 | 256 | ||
| 242 | @Override | 257 | @Override |
| 243 | - public void updateCustomerFollowUp(CrmUpdateFollowUpReqBO customerUpdateFollowUpReqBO) { | ||
| 244 | - customerMapper.updateById(BeanUtils.toBean(customerUpdateFollowUpReqBO, CrmCustomerDO.class).setId(customerUpdateFollowUpReqBO.getBizId())); | ||
| 245 | - } | ||
| 246 | - | ||
| 247 | - @Override | ||
| 248 | @Transactional(rollbackFor = Exception.class) | 258 | @Transactional(rollbackFor = Exception.class) |
| 249 | @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}", | 259 | @LogRecord(type = CRM_CUSTOMER_TYPE, subType = CRM_CUSTOMER_CREATE_SUB_TYPE, bizNo = "{{#customer.id}}", |
| 250 | success = CRM_CUSTOMER_CREATE_SUCCESS) | 260 | success = CRM_CUSTOMER_CREATE_SUCCESS) |
| @@ -263,7 +273,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | @@ -263,7 +273,8 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | ||
| 263 | } | 273 | } |
| 264 | 274 | ||
| 265 | @Override | 275 | @Override |
| 266 | - public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers, CrmCustomerImportReqVO importReqVO) { | 276 | + public CrmCustomerImportRespVO importCustomerList(List<CrmCustomerImportExcelVO> importCustomers, |
| 277 | + CrmCustomerImportReqVO importReqVO) { | ||
| 267 | if (CollUtil.isEmpty(importCustomers)) { | 278 | if (CollUtil.isEmpty(importCustomers)) { |
| 268 | throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY); | 279 | throw exception(CUSTOMER_IMPORT_LIST_IS_EMPTY); |
| 269 | } | 280 | } |
| @@ -383,12 +394,13 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | @@ -383,12 +394,13 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | ||
| 383 | // 1.4 校验负责人是否到达上限 | 394 | // 1.4 校验负责人是否到达上限 |
| 384 | validateCustomerExceedOwnerLimit(ownerUserId, customers.size()); | 395 | validateCustomerExceedOwnerLimit(ownerUserId, customers.size()); |
| 385 | 396 | ||
| 386 | - // 2.1 领取公海数据 | 397 | + // 2. 领取公海数据 |
| 387 | List<CrmCustomerDO> updateCustomers = new ArrayList<>(); | 398 | List<CrmCustomerDO> updateCustomers = new ArrayList<>(); |
| 388 | List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>(); | 399 | List<CrmPermissionCreateReqBO> createPermissions = new ArrayList<>(); |
| 389 | customers.forEach(customer -> { | 400 | customers.forEach(customer -> { |
| 390 | // 2.1. 设置负责人 | 401 | // 2.1. 设置负责人 |
| 391 | - updateCustomers.add(new CrmCustomerDO().setId(customer.getId()).setOwnerUserId(ownerUserId)); | 402 | + updateCustomers.add(new CrmCustomerDO().setId(customer.getId()) |
| 403 | + .setOwnerUserId(ownerUserId).setOwnerTime(LocalDateTime.now())); | ||
| 392 | // 2.2. 创建负责人数据权限 | 404 | // 2.2. 创建负责人数据权限 |
| 393 | createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) | 405 | createPermissions.add(new CrmPermissionCreateReqBO().setBizType(CrmBizTypeEnum.CRM_CUSTOMER.getType()) |
| 394 | .setBizId(customer.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); | 406 | .setBizId(customer.getId()).setUserId(ownerUserId).setLevel(CrmPermissionLevelEnum.OWNER.getLevel())); |
| @@ -415,34 +427,23 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | @@ -415,34 +427,23 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | ||
| 415 | if (poolConfig == null || !poolConfig.getEnabled()) { | 427 | if (poolConfig == null || !poolConfig.getEnabled()) { |
| 416 | return 0; | 428 | return 0; |
| 417 | } | 429 | } |
| 418 | - // 1.1 获取没有锁定的不在公海的客户 | ||
| 419 | - List<CrmCustomerDO> customerList = customerMapper.selectListByLockAndNotPool(Boolean.FALSE); | ||
| 420 | - // TODO @puhui999:下面也搞到 sql 里去哈;写 or 查询,问题不大的;低 393 到 402;原因是,避免无用的太多数据查询到 java 进程里; | ||
| 421 | - List<CrmCustomerDO> poolCustomerList = new ArrayList<>(); | ||
| 422 | - poolCustomerList.addAll(filterList(customerList, customer -> | ||
| 423 | - !customer.getDealStatus() && (poolConfig.getDealExpireDays() - LocalDateTimeUtils.between(customer.getCreateTime())) <= 0)); | ||
| 424 | - poolCustomerList.addAll(filterList(customerList, customer -> { | ||
| 425 | - if (!customer.getDealStatus()) { // 这里只处理成交的 | ||
| 426 | - return false; | ||
| 427 | - } | ||
| 428 | - LocalDateTime lastTime = ObjUtil.defaultIfNull(customer.getContactLastTime(), customer.getCreateTime()); | ||
| 429 | - return (poolConfig.getContactExpireDays() - LocalDateTimeUtils.between(lastTime)) <= 0; | ||
| 430 | - })); | ||
| 431 | - | 430 | + // 1. 获得需要放到的客户列表 |
| 431 | + List<CrmCustomerDO> customerList = customerMapper.selectListByAutoPool(poolConfig); | ||
| 432 | // 2. 逐个放入公海 | 432 | // 2. 逐个放入公海 |
| 433 | int count = 0; | 433 | int count = 0; |
| 434 | - for (CrmCustomerDO customer : poolCustomerList) { | 434 | + for (CrmCustomerDO customer : customerList) { |
| 435 | try { | 435 | try { |
| 436 | getSelf().putCustomerPool(customer); | 436 | getSelf().putCustomerPool(customer); |
| 437 | count++; | 437 | count++; |
| 438 | } catch (Throwable e) { | 438 | } catch (Throwable e) { |
| 439 | - log.error("[autoPutCustomerPool][Customer 客户({}) 放入公海异常]", customer.getId(), e); | 439 | + log.error("[autoPutCustomerPool][客户({}) 放入公海异常]", customer.getId(), e); |
| 440 | } | 440 | } |
| 441 | } | 441 | } |
| 442 | return count; | 442 | return count; |
| 443 | } | 443 | } |
| 444 | 444 | ||
| 445 | - private void putCustomerPool(CrmCustomerDO customer) { | 445 | + @Transactional // 需要 protected 修饰,因为需要在事务中调用 |
| 446 | + protected void putCustomerPool(CrmCustomerDO customer) { | ||
| 446 | // 1. 设置负责人为 NULL | 447 | // 1. 设置负责人为 NULL |
| 447 | int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null); | 448 | int updateOwnerUserIncr = customerMapper.updateOwnerUserIdById(customer.getId(), null); |
| 448 | if (updateOwnerUserIncr == 0) { | 449 | if (updateOwnerUserIncr == 0) { |
| @@ -486,17 +487,29 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | @@ -486,17 +487,29 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | ||
| 486 | } | 487 | } |
| 487 | 488 | ||
| 488 | @Override | 489 | @Override |
| 489 | - public PageResult<CrmCustomerDO> getPutInPoolRemindCustomerPage(CrmCustomerPageReqVO pageReqVO, | ||
| 490 | - CrmCustomerPoolConfigDO poolConfigDO, | ||
| 491 | - Long userId) { | ||
| 492 | - return customerMapper.selectPutInPoolRemindCustomerPage(pageReqVO, poolConfigDO, userId); | 490 | + public PageResult<CrmCustomerDO> getPutPoolRemindCustomerPage(CrmCustomerPageReqVO pageVO, Long userId) { |
| 491 | + CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); | ||
| 492 | + if (ObjUtil.isNull(poolConfig) | ||
| 493 | + || Boolean.FALSE.equals(poolConfig.getEnabled()) | ||
| 494 | + || Boolean.FALSE.equals(poolConfig.getNotifyEnabled())) { | ||
| 495 | + return PageResult.empty(); | ||
| 496 | + } | ||
| 497 | + return customerMapper.selectPutPoolRemindCustomerPage(pageVO, poolConfig, userId); | ||
| 493 | } | 498 | } |
| 494 | 499 | ||
| 495 | @Override | 500 | @Override |
| 496 | - public Long getPutInPoolRemindCustomerCount(CrmCustomerPageReqVO pageReqVO, | ||
| 497 | - CrmCustomerPoolConfigDO poolConfigDO, | ||
| 498 | - Long userId) { | ||
| 499 | - return customerMapper.selectPutInPoolRemindCustomerCount(pageReqVO, poolConfigDO, userId); | 501 | + public Long getPutPoolRemindCustomerCount(Long userId) { |
| 502 | + CrmCustomerPoolConfigDO poolConfig = customerPoolConfigService.getCustomerPoolConfig(); | ||
| 503 | + if (ObjUtil.isNull(poolConfig) | ||
| 504 | + || Boolean.FALSE.equals(poolConfig.getEnabled()) | ||
| 505 | + || Boolean.FALSE.equals(poolConfig.getNotifyEnabled())) { | ||
| 506 | + return 0L; | ||
| 507 | + } | ||
| 508 | + CrmCustomerPageReqVO pageVO = new CrmCustomerPageReqVO() | ||
| 509 | + .setPool(null) | ||
| 510 | + .setContactStatus(CrmCustomerPageReqVO.CONTACT_TODAY) | ||
| 511 | + .setSceneType(CrmSceneTypeEnum.OWNER.getType()); | ||
| 512 | + return customerMapper.selectPutPoolRemindCustomerCount(pageVO, poolConfig, userId); | ||
| 500 | } | 513 | } |
| 501 | 514 | ||
| 502 | @Override | 515 | @Override |
| @@ -596,7 +609,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | @@ -596,7 +609,6 @@ public class CrmCustomerServiceImpl implements CrmCustomerService { | ||
| 596 | } | 609 | } |
| 597 | } | 610 | } |
| 598 | 611 | ||
| 599 | - | ||
| 600 | /** | 612 | /** |
| 601 | * 获得自身的代理对象,解决 AOP 生效问题 | 613 | * 获得自身的代理对象,解决 AOP 生效问题 |
| 602 | * | 614 | * |
yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java
| @@ -72,24 +72,24 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService { | @@ -72,24 +72,24 @@ public class CrmFollowUpRecordServiceImpl implements CrmFollowUpRecordService { | ||
| 72 | CrmFollowUpRecordDO followUpRecord = BeanUtils.toBean(createReqVO, CrmFollowUpRecordDO.class); | 72 | CrmFollowUpRecordDO followUpRecord = BeanUtils.toBean(createReqVO, CrmFollowUpRecordDO.class); |
| 73 | crmFollowUpRecordMapper.insert(followUpRecord); | 73 | crmFollowUpRecordMapper.insert(followUpRecord); |
| 74 | 74 | ||
| 75 | - LocalDateTime now = LocalDateTime.now(); | ||
| 76 | - CrmUpdateFollowUpReqBO updateFollowUpReqBO = new CrmUpdateFollowUpReqBO().setBizId(followUpRecord.getBizId()) | ||
| 77 | - .setContactLastTime(now).setContactNextTime(followUpRecord.getNextTime()).setContactLastContent(followUpRecord.getContent()); | ||
| 78 | // 2. 更新 bizId 对应的记录; | 75 | // 2. 更新 bizId 对应的记录; |
| 79 | - if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_BUSINESS.getType(), followUpRecord.getBizType())) { // 更新商机跟进信息 | 76 | + CrmUpdateFollowUpReqBO updateFollowUpReqBO = new CrmUpdateFollowUpReqBO().setBizId(followUpRecord.getBizId()) |
| 77 | + .setContactLastTime(LocalDateTime.now()) | ||
| 78 | + .setContactNextTime(followUpRecord.getNextTime()).setContactLastContent(followUpRecord.getContent()); | ||
| 79 | + if (ObjUtil.equal(CrmBizTypeEnum.CRM_BUSINESS.getType(), followUpRecord.getBizType())) { // 更新商机跟进信息 | ||
| 80 | businessService.updateBusinessFollowUpBatch(Collections.singletonList(updateFollowUpReqBO)); | 80 | businessService.updateBusinessFollowUpBatch(Collections.singletonList(updateFollowUpReqBO)); |
| 81 | } | 81 | } |
| 82 | - if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CLUE.getType(), followUpRecord.getBizType())) { // 更新线索跟进信息 | 82 | + if (ObjUtil.equal(CrmBizTypeEnum.CRM_CLUE.getType(), followUpRecord.getBizType())) { // 更新线索跟进信息 |
| 83 | clueService.updateClueFollowUp(followUpRecord.getId(), followUpRecord.getNextTime(), followUpRecord.getContent()); | 83 | clueService.updateClueFollowUp(followUpRecord.getId(), followUpRecord.getNextTime(), followUpRecord.getContent()); |
| 84 | } | 84 | } |
| 85 | - if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CONTACT.getType(), followUpRecord.getBizType())) { // 更新联系人跟进信息 | 85 | + if (ObjUtil.equal(CrmBizTypeEnum.CRM_CONTACT.getType(), followUpRecord.getBizType())) { // 更新联系人跟进信息 |
| 86 | contactService.updateContactFollowUpBatch(Collections.singletonList(updateFollowUpReqBO)); | 86 | contactService.updateContactFollowUpBatch(Collections.singletonList(updateFollowUpReqBO)); |
| 87 | } | 87 | } |
| 88 | - if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CONTRACT.getType(), followUpRecord.getBizType())) { // 更新合同跟进信息 | 88 | + if (ObjUtil.equal(CrmBizTypeEnum.CRM_CONTRACT.getType(), followUpRecord.getBizType())) { // 更新合同跟进信息 |
| 89 | contractService.updateContractFollowUp(updateFollowUpReqBO); | 89 | contractService.updateContractFollowUp(updateFollowUpReqBO); |
| 90 | } | 90 | } |
| 91 | - if (ObjUtil.notEqual(CrmBizTypeEnum.CRM_CUSTOMER.getType(), followUpRecord.getBizType())) { // 更新客户跟进信息 | ||
| 92 | - customerService.updateCustomerFollowUp(updateFollowUpReqBO); | 91 | + if (ObjUtil.equal(CrmBizTypeEnum.CRM_CUSTOMER.getType(), followUpRecord.getBizType())) { // 更新客户跟进信息 |
| 92 | + customerService.updateCustomerFollowUp(followUpRecord.getBizId(), followUpRecord.getNextTime(), followUpRecord.getContent()); | ||
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | // 3.1 更新 contactIds 对应的记录,不更新 lastTime 和 lastContent | 95 | // 3.1 更新 contactIds 对应的记录,不更新 lastTime 和 lastContent |