feat(crm): 添加跟进记录功能并优化字典数据相关接口- 新增跟进记录相关功能,包括创建、编辑、删除和查询跟进记录

- 添加跟进用户记录相关功能和接口
- 优化字典数据相关接口,支持批量获取字典数据
- 修复加班定时任务相关代码,提高异常处理能力
This commit is contained in:
aikai 2025-01-20 10:28:36 +08:00
parent c2af2a8374
commit e7376d5b6e
20 changed files with 319 additions and 57 deletions

View File

@ -44,15 +44,22 @@ public class WorkOvertimeJob {
LocalDateTime now = LocalDateTime.now();
List<BpmOAOvertimeDO> bpmOAOvertimeDOS = overtimeMapper.selectList(new LambdaQueryWrapper<BpmOAOvertimeDO>()
.eq(BpmOAOvertimeDO::getSettlementFlag, 0)
.eq(BpmOAOvertimeDO::getResult, BpmProcessInstanceResultEnum.APPROVE)
.ge(BpmOAOvertimeDO::getEndTime, now));
.eq(BpmOAOvertimeDO::getResult, BpmProcessInstanceResultEnum.APPROVE.getResult())
.le(BpmOAOvertimeDO::getEndTime, now));
if (CollectionUtil.isNotEmpty(bpmOAOvertimeDOS)) {
List<Long> ids = bpmOAOvertimeDOS.stream().map(BpmOAOvertimeDO::getId).collect(Collectors.toList());
List<BpmOAOvertimeItemDO> items = overtimeItemService.getByOvertimeIds(ids);
Map<Long, List<BpmOAOvertimeItemDO>> map = items.stream().collect(Collectors.groupingBy(BpmOAOvertimeItemDO::getOaOvertimeId));
for (BpmOAOvertimeDO bpmOAOvertimeDO : bpmOAOvertimeDOS) {
List<BpmOAOvertimeItemDO> bpmOAOvertimeItemDOS = map.get(bpmOAOvertimeDO.getId());
if (CollectionUtil.isEmpty(bpmOAOvertimeItemDOS)) {
continue;
}
try {
overtimeService.handlingOvertime(bpmOAOvertimeDO, bpmOAOvertimeItemDOS, now);
} catch (Exception e) {
log.error("处理加班失败", e);
}
}
}
log.info("结束 加班定时任务");

View File

@ -1,11 +1,13 @@
package cn.iocoder.yudao.module.crm.controller.admin.crmrecord.vo;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 跟进记录 Response VO")
@Data
@ -58,4 +60,8 @@ public class CrmRecordRespVO {
private String ownUserName;
@Schema(description = "跟进用户列表")
@ExcelProperty("跟进用户列表")
private List<AdminUserRespDTO> adminUserRespDTOS;
}

View File

@ -1,10 +1,13 @@
package cn.iocoder.yudao.module.crm.controller.admin.crmrecord.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import javax.validation.constraints.*;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Schema(description = "管理后台 - 跟进记录新增/修改 Request VO")
@Data
@ -40,4 +43,7 @@ public class CrmRecordSaveReqVO {
@Schema(description = "跟进状态")
private Integer followStatus;
@Schema(description = "跟进用户ids")
private List<Long> userIds = new ArrayList<>();
}

View File

@ -2,11 +2,13 @@ package cn.iocoder.yudao.module.crm.dal.dataobject.crmrecord;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.time.LocalDateTime;
import java.util.List;
/**
* 跟进记录 DO

View File

@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.crm.dal.dataobject.crmrecorduser;
import lombok.*;
import com.baomidou.mybatisplus.annotation.*;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import lombok.experimental.Accessors;
/**
* 跟进用户记录 DO
*
* @author 艾楷
*/
@TableName("crm_record_user")
@KeySequence("crm_record_user_seq") // 用于 OraclePostgreSQLKingbaseDB2H2 数据库的主键自增如果是 MySQL 等数据库可不写
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)
public class CrmRecordUserDO extends BaseDO {
/**
* id
*/
@TableId
private Long id;
/**
* 跟进记录id
*/
private Long recordId;
/**
* 跟进用户id
*/
private Long userId;
}

View File

@ -31,5 +31,8 @@ public interface CrmRecordMapper extends BaseMapperX<CrmRecordDO> {
.orderByDesc(CrmRecordDO::getId));
}
List<RecordStatisticsRespVO> selectStatistics(@Param("userIds") List<Long> userIds, @Param("createTime") LocalDateTime[] createTime);
}

View File

@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.crm.dal.mysql.crmrecorduser;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmrecorduser.CrmRecordUserDO;
import org.apache.ibatis.annotations.Mapper;
/**
* 跟进用户记录 Mapper
*
* @author 艾楷
*/
@Mapper
public interface CrmRecordUserMapper extends BaseMapperX<CrmRecordUserDO> {
}

View File

@ -12,14 +12,8 @@ import cn.iocoder.yudao.module.crm.controller.admin.crmanalysis.vo.UserVolumeVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmcustomer.vo.CustomerStatisticRespVO;
import cn.iocoder.yudao.module.crm.controller.admin.crmrecord.vo.RecordStatisticsRespVO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmcustomer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.mysql.crmachievement.CrmAchievementMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmclues.CrmCluesMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmcontract.CrmContractMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmcontractreceivables.CrmContractReceivablesMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmcustomer.CrmCustomerMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmrecord.CrmRecordMapper;
import cn.iocoder.yudao.module.crm.service.crmrecord.CrmRecordService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@ -175,14 +169,11 @@ public class CustomerServiceImpl implements CustomerService {
PageResult<AdminUserApiVO> pageResult = adminUserApi.getUserPageByRole(dto).getCheckedData();
PageResult<UserRecordVO> pageResult1 = BeanUtils.toBean(pageResult, UserRecordVO.class);
if (CollectionUtil.isNotEmpty(pageResult1.getList())) {
// 获取所有用户跟进统计列表
List<Long> userIds = convertList(pageResult1.getList(), UserRecordVO::getId);
List<RecordStatisticsRespVO> respVOS = recordService.getRecordStatistics(userIds, pageReqVO.getCreateTime());
Map<String, RecordStatisticsRespVO> recordMap = convertMap(respVOS, RecordStatisticsRespVO::getCreator);
pageResult1.getList().forEach(v -> {
RecordStatisticsRespVO respVO = recordMap.get(v.getId().toString());
if (respVO == null) {
// 设置跟进总数量
@ -205,7 +196,6 @@ public class CustomerServiceImpl implements CustomerService {
}
});
}
return pageResult1;
}

View File

@ -1,6 +1,9 @@
package cn.iocoder.yudao.module.crm.service.crmrecord;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.module.crm.controller.admin.crmrecord.vo.CrmRecordPageReqVO;
@ -11,16 +14,21 @@ import cn.iocoder.yudao.module.crm.dal.dataobject.crmbusiness.CrmBusinessDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmclues.CrmCluesDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmcustomer.CrmCustomerDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmrecord.CrmRecordDO;
import cn.iocoder.yudao.module.crm.dal.dataobject.crmrecorduser.CrmRecordUserDO;
import cn.iocoder.yudao.module.crm.dal.mysql.crmbusiness.CrmBusinessMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmclues.CrmCluesMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmcustomer.CrmCustomerMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmrecord.CrmRecordMapper;
import cn.iocoder.yudao.module.crm.dal.mysql.crmrecorduser.CrmRecordUserMapper;
import cn.iocoder.yudao.module.hrm.enums.RelationEnum;
import cn.iocoder.yudao.module.hrm.enums.TypesEnum;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
import cn.iocoder.yudao.module.system.api.dict.dto.DictParameterRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@ -28,9 +36,13 @@ import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
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.hrm.enums.ErrorCodeConstants.RECORD_NOT_EXISTS;
/**
@ -45,6 +57,8 @@ public class CrmRecordServiceImpl implements CrmRecordService {
@Resource
private CrmRecordMapper recordMapper;
@Resource
private CrmRecordUserMapper crmRecordUserMapper;
@Resource
private CrmCustomerMapper customerMapper;
@Resource
private AdminUserApi adminUserApi;
@ -61,7 +75,14 @@ public class CrmRecordServiceImpl implements CrmRecordService {
// 插入
CrmRecordDO record = BeanUtils.toBean(createReqVO, CrmRecordDO.class);
recordMapper.insert(record);
// -- 插入跟进用户
List<CrmRecordUserDO> recordUsers = new ArrayList<>();
for (Long userId : createReqVO.getUserIds()) {
recordUsers.add(new CrmRecordUserDO().setRecordId(record.getId()).setUserId(userId));
}
if (!recordUsers.isEmpty()) {
crmRecordUserMapper.insertBatch(recordUsers);
}
//更新客户
if (TypesEnum.CUSTOMER.getValue().equals(createReqVO.getTypes())) {
customerMapper.updateById(CrmCustomerDO.builder()
@ -89,8 +110,6 @@ public class CrmRecordServiceImpl implements CrmRecordService {
.followTime(LocalDateTime.now())
.build());
}
// 返回
return record.getId();
}
@ -102,15 +121,43 @@ public class CrmRecordServiceImpl implements CrmRecordService {
// 更新
CrmRecordDO updateObj = BeanUtils.toBean(updateReqVO, CrmRecordDO.class);
recordMapper.updateById(updateObj);
// -- 编辑更近用户的话
List<CrmRecordUserDO> oldCrmRecordUsers = crmRecordUserMapper.selectList(new LambdaQueryWrapper<CrmRecordUserDO>()
.eq(CrmRecordUserDO::getRecordId, updateReqVO.getId()));
List<Long> oldUserIds = oldCrmRecordUsers.stream().map(CrmRecordUserDO::getUserId).collect(Collectors.toList());
List<Long> userIds = updateReqVO.getUserIds();
List<List<Long>> list = CollectionUtils.diffList(oldUserIds, userIds,
ObjectUtil::equal
);
if (CollUtil.isNotEmpty(list.get(0))) {
List<CrmRecordUserDO> crmRecordUserDOS = new ArrayList<>();
for (Long userId : list.get(0)) {
CrmRecordUserDO crmRecordUserDO = new CrmRecordUserDO();
crmRecordUserDO.setRecordId(updateReqVO.getId());
crmRecordUserDO.setUserId(userId);
crmRecordUserDOS.add(crmRecordUserDO);
}
crmRecordUserMapper.insertBatch(crmRecordUserDOS);
}
if (CollUtil.isNotEmpty(list.get(2))) {
crmRecordUserMapper.delete(new LambdaQueryWrapper<CrmRecordUserDO>()
.in(CrmRecordUserDO::getUserId, list.get(2)));
}
}
@Override
public void deleteRecord(Long id) {
// 校验存在
validateRecordExists(id);
// 删除
// 删除该记录下的跟进用户
Long adminId = SecurityFrameworkUtils.getLoginUserId();
List<CrmRecordUserDO> crmRecordUserDOS = crmRecordUserMapper.selectList(new LambdaQueryWrapper<CrmRecordUserDO>().eq(CrmRecordUserDO::getRecordId, id));
crmRecordUserMapper.delete(new LambdaQueryWrapper<CrmRecordUserDO>().eq(CrmRecordUserDO::getRecordId, id).eq(CrmRecordUserDO::getUserId, adminId));
// 删除如果是最后一个的话删除一下
if (crmRecordUserDOS.size() == 1 && adminId.equals(crmRecordUserDOS.get(0).getUserId())){
recordMapper.deleteById(id);
}
}
private void validateRecordExists(Long id) {
if (recordMapper.selectById(id) == null) {
@ -132,26 +179,88 @@ public class CrmRecordServiceImpl implements CrmRecordService {
} else if (RelationEnum.SUB.getValue().equals(pageReqVO.getRelation())) {
ids = adminUserApi.getUserListBySubordinateIds(adminId).getCheckedData();
}
PageResult<CrmRecordDO> pageResult = recordMapper.selectPage(pageReqVO, ids);
LocalDateTime beginTime = pageReqVO.getNextTime() != null && pageReqVO.getNextTime().length > 0 ? pageReqVO.getNextTime()[0] : null;
LocalDateTime endTime = pageReqVO.getNextTime() != null && pageReqVO.getNextTime().length > 0 ? pageReqVO.getNextTime()[1] : null;
MPJLambdaWrapper<CrmRecordDO> wrapper = new MPJLambdaWrapper<CrmRecordDO>()
.selectAll(CrmRecordDO.class)
.leftJoin(CrmRecordUserDO.class, CrmRecordUserDO::getRecordId, CrmRecordDO::getId)
.eqIfExists(CrmRecordDO::getTypes, pageReqVO.getTypes())
.eqIfExists(CrmRecordDO::getTypesId, pageReqVO.getTypesId())
.eqIfExists(CrmRecordDO::getContent, pageReqVO.getContent())
.eqIfExists(CrmRecordDO::getRecordType, pageReqVO.getRecordType())
.ge(beginTime != null, CrmRecordDO::getNextTime, beginTime)
.le(endTime != null, CrmRecordDO::getNextTime, endTime)
.in(CollUtil.isNotEmpty(ids), CrmRecordUserDO::getUserId, ids)
.groupBy(CrmRecordDO::getId)
.orderByDesc(CrmRecordDO::getId);
PageResult<CrmRecordDO> pageResult = recordMapper.selectJoinPage(pageReqVO, CrmRecordDO.class, wrapper);
PageResult<CrmRecordRespVO> pageResult1 = BeanUtils.toBean(pageResult, CrmRecordRespVO.class);
List<CrmRecordRespVO> list = pageResult1.getList();
Map<Long, List<CrmRecordUserDO>> crmRecordUserMap = new HashMap<>();
List<CrmRecordUserDO> crmRecordUserList = new ArrayList<>();
if (CollUtil.isNotEmpty(list)) {
crmRecordUserList = crmRecordUserMapper.selectList(new LambdaQueryWrapper<CrmRecordUserDO>().in(CrmRecordUserDO::getRecordId, list.stream().map(CrmRecordRespVO::getId).collect(Collectors.toList())));
crmRecordUserMap = crmRecordUserList.stream().collect(Collectors.groupingBy(CrmRecordUserDO::getRecordId));
}
for (CrmRecordRespVO crmRecordRespVO : pageResult1.getList()) {
DictDataRespDTO dto = dictDataApi.getDictData("follow_status", crmRecordRespVO.getRecordType().toString()).getCheckedData();
List<String> recordTypes = list.stream().map(CrmRecordRespVO::getRecordType).map(String::valueOf).collect(Collectors.toList());
List<DictDataRespDTO> respDTOS = CollUtil.isNotEmpty(recordTypes) ? dictDataApi.getByValues(new DictParameterRespDTO().setDictType("follow_status").setValues(recordTypes)).getCheckedData() : new ArrayList<>();
Map<String, DictDataRespDTO> dictDataMap = CollectionUtils.convertMap(respDTOS, DictDataRespDTO::getValue);
Map<String, List<Long>> typeIdMap = list.stream().collect(Collectors.groupingBy(CrmRecordRespVO::getTypes, Collectors.mapping(CrmRecordRespVO::getTypesId, Collectors.toList())));
// 批量查询客户业务和线索数据
Map<Long, CrmCustomerDO> customerMap = new HashMap<>();
if (CollUtil.isNotEmpty(typeIdMap.get(TypesEnum.CUSTOMER.getValue()))) {
customerMap = customerMapper.selectBatchIds(typeIdMap.get(TypesEnum.CUSTOMER.getValue())).stream()
.collect(Collectors.toMap(CrmCustomerDO::getId, item -> item));
}
Map<Long, CrmBusinessDO> businessMap = new HashMap<>();
if (CollUtil.isNotEmpty(typeIdMap.get(TypesEnum.BUSINESS.getValue()))) {
businessMap = crmBusinessMapper.selectBatchIds(typeIdMap.get(TypesEnum.BUSINESS.getValue())).stream()
.collect(Collectors.toMap(CrmBusinessDO::getId, item -> item));
}
Map<Long, CrmCluesDO> cluesMap = new HashMap<>();
if (CollUtil.isNotEmpty(typeIdMap.get(TypesEnum.CLUES.getValue()))) {
cluesMap = cluesMapper.selectBatchIds(typeIdMap.get(TypesEnum.CLUES.getValue())).stream()
.collect(Collectors.toMap(CrmCluesDO::getId, item -> item));
}
List<Long> userIds = crmRecordUserList.stream().map(CrmRecordUserDO::getUserId).distinct().collect(Collectors.toList());
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserList(userIds).getCheckedData().stream().collect(Collectors.toMap(AdminUserRespDTO::getId, item -> item));
for (CrmRecordRespVO crmRecordRespVO : list) {
// 设置记录类型名称
DictDataRespDTO dto = dictDataMap.get(crmRecordRespVO.getRecordType().toString());
if (dto != null) {
crmRecordRespVO.setRecordTypeName(dto.getLabel());
if (TypesEnum.CUSTOMER.getValue().equals(crmRecordRespVO.getTypes())) {
CrmCustomerDO customerDO = customerMapper.selectById(crmRecordRespVO.getTypesId());
}
// 根据类型设置名称
String types = crmRecordRespVO.getTypes();
if (TypesEnum.CUSTOMER.getValue().equals(types)) {
CrmCustomerDO customerDO = customerMap.get(crmRecordRespVO.getTypesId());
if (customerDO != null) {
crmRecordRespVO.setTypesName(customerDO.getName());
} else if (TypesEnum.BUSINESS.getValue().equals(crmRecordRespVO.getTypes())) {
CrmBusinessDO crmBusinessDO = crmBusinessMapper.selectById(crmRecordRespVO.getTypesId());
}
} else if (TypesEnum.BUSINESS.getValue().equals(types)) {
CrmBusinessDO crmBusinessDO = businessMap.get(crmRecordRespVO.getTypesId());
if (crmBusinessDO != null) {
crmRecordRespVO.setTypesName(crmBusinessDO.getName());
} else if (TypesEnum.CLUES.getValue().equals(crmRecordRespVO.getTypes())) {
CrmCluesDO crmCluesDO = cluesMapper.selectById(crmRecordRespVO.getTypesId());
}
} else if (TypesEnum.CLUES.getValue().equals(types)) {
CrmCluesDO crmCluesDO = cluesMap.get(crmRecordRespVO.getTypesId());
if (crmCluesDO != null) {
crmRecordRespVO.setTypesName(crmCluesDO.getName());
}
AdminUserRespDTO adminUserRespDTO = adminUserApi.getUser(crmRecordRespVO.getCreator()).getCheckedData();
crmRecordRespVO.setOwnUserName(adminUserRespDTO.getNickname());
}
// 获取创建者信息
List<CrmRecordUserDO> crmRecordUserDOS = crmRecordUserMap.get(crmRecordRespVO.getId());
List<AdminUserRespDTO> adminUserRespDTOS = new ArrayList<>();
if (CollUtil.isEmpty(crmRecordUserDOS)) {
crmRecordRespVO.setAdminUserRespDTOS(adminUserRespDTOS);
continue;
}
for (CrmRecordUserDO crmRecordUserDO : crmRecordUserDOS) {
AdminUserRespDTO adminUserRespDTO = userMap.get(crmRecordUserDO.getUserId());
adminUserRespDTOS.add(adminUserRespDTO);
}
crmRecordRespVO.setAdminUserRespDTOS(adminUserRespDTOS);
}
return pageResult1;
}

View File

@ -0,0 +1,10 @@
package cn.iocoder.yudao.module.crm.service.crmrecorduser;
/**
* 跟进用户记录 Service 接口
*
* @author 艾楷
*/
public interface CrmRecordUserService {
}

View File

@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.crm.service.crmrecorduser;
import cn.iocoder.yudao.module.crm.dal.mysql.crmrecorduser.CrmRecordUserMapper;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import javax.annotation.Resource;
/**
* 跟进用户记录 Service 实现类
*
* @author 艾楷
*/
@Service
@Validated
public class CrmRecordUserServiceImpl implements CrmRecordUserService {
@Resource
private CrmRecordUserMapper crmRecordUserMapper;
}

View File

@ -11,27 +11,30 @@
<select id="selectStatistics" resultType="cn.iocoder.yudao.module.crm.controller.admin.crmrecord.vo.RecordStatisticsRespVO">
SELECT
creator AS creator,
COUNT(1) AS count,
SUM(CASE WHEN types = 'business' THEN 1 ELSE 0 END) AS businessCount,
SUM(CASE WHEN types = 'customer' THEN 1 ELSE 0 END) AS customerCount,
SUM(CASE WHEN types = 'clues' THEN 1 ELSE 0 END) AS cluesCount
b.user_id AS creator,
COUNT( a.id ) AS count,
SUM( CASE WHEN a.types = 'business' THEN 1 ELSE 0 END ) AS businessCount,
SUM( CASE WHEN a.types = 'customer' THEN 1 ELSE 0 END ) AS customerCount,
SUM( CASE WHEN a.types = 'clues' THEN 1 ELSE 0 END ) AS cluesCount
FROM
crm_record
WHERE
deleted = 0
AND creator IN
crm_record as a
LEFT JOIN crm_record_user as b on a.id = b.record_id
<where>
a.deleted = 0
and b.deleted = 0
AND b.user_id IN
<foreach item="userId" collection="userIds" separator="," open="(" close=")" index="">
#{userId}
</foreach>
<if test="createTime != null and createTime.length > 0">
<if test="createTime[0] != null">
AND create_time &gt;= #{createTime[0]}
AND b.create_time &gt;= #{createTime[0]}
</if>
<if test="createTime[1] != null">
AND create_time &lt;= #{createTime[1]}
AND b.create_time &lt;= #{createTime[1]}
</if>
</if>
GROUP BY creator
GROUP BY b.user_id
</where>
</select>
</mapper>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.iocoder.yudao.module.crm.dal.mysql.recorduser.RecordUserMapper">
<!--
一般情况下,尽可能使用 Mapper 进行 CRUD 增删改查即可。
无法满足的场景,例如说多表关联查询,才使用 XML 编写 SQL。
代码生成器暂时只生成 Mapper XML 文件本身,更多推荐 MybatisX 快速开发插件来生成查询。
文档可见https://www.iocoder.cn/MyBatis/x-plugins/
-->
</mapper>

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.api.dict;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
import cn.iocoder.yudao.module.system.api.dict.dto.DictParameterRespDTO;
import cn.iocoder.yudao.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -9,6 +10,8 @@ import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.Collection;
@ -38,6 +41,12 @@ public interface DictDataApi {
CommonResult<DictDataRespDTO> getDictData(@RequestParam("dictType") String dictType,
@RequestParam("value") String value);
@PostMapping(PREFIX + "/getByValues")
@Operation(summary = "获得指定的字典数据-(值多选筛选)")
CommonResult<List<DictDataRespDTO>> getByValues(@RequestBody DictParameterRespDTO dto);
@GetMapping(PREFIX + "/parse")
@Operation(summary = "解析获得指定的字典数据")
@Parameters({

View File

@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.system.api.dict.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "RPC 服务 - 字典数据 Response DTO")
@Data
public class DictParameterRespDTO {
@Schema(description = "字典值", requiredMode = Schema.RequiredMode.REQUIRED, example = "iocoder")
private List<String> values;
@Schema(description = "字典类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "sys_common_sex")
private String dictType;
}

View File

@ -1,9 +1,11 @@
package cn.iocoder.yudao.module.system.api.dict;
import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.system.api.dict.dto.DictDataRespDTO;
import cn.iocoder.yudao.module.system.api.dict.dto.DictParameterRespDTO;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import cn.iocoder.yudao.module.system.service.dict.DictDataService;
import org.springframework.validation.annotation.Validated;
@ -34,6 +36,12 @@ public class DictDataApiImpl implements DictDataApi {
return success(BeanUtils.toBean(dictData, DictDataRespDTO.class));
}
@Override
public CommonResult<List<DictDataRespDTO>> getByValues(DictParameterRespDTO dto) {
List<DictDataDO> list = dictDataService.getDictDataList(dto.getDictType(), dto.getValues());
return success(BeanUtil.copyToList(list, DictDataRespDTO.class));
}
@Override
public CommonResult<DictDataRespDTO> parseDictData(String dictType, String label) {
DictDataDO dictData = dictDataService.parseDictData(dictType, label);

View File

@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.dict;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.api.dict.dto.DictParameterRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

View File

@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.system.service.dict;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.api.dict.dto.DictParameterRespDTO;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;

View File

@ -854,7 +854,7 @@ public class AdminUserServiceImpl implements AdminUserService {
List<DeptDO> deptList = deptService.getAllList();
Map<Long, List<DeptDO>> map = deptList.stream().collect(Collectors.groupingBy(DeptDO::getParentId));
this.getSubordinateDeptIds(map, nextIds, ids);
nextIds.add(dept.getId());
nextIds = nextIds.stream().distinct().collect(Collectors.toList());
// 2. 获取部门对应的用户信息
List<AdminUserDO> users = this.getUserListByDeptIds(nextIds, null);

View File

@ -41,16 +41,16 @@ spring:
datasource:
master:
name: ruoyi-vue-pro
url: jdbc:mysql://47.97.8.94:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
url: jdbc:mysql://rm-bp1yloyj508qld78jno.mysql.rds.aliyuncs.com:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
driver-class-name: com.mysql.jdbc.Driver
username: root
password: yhtkj@2024!
password: Znalyrds2024
slave: # 模拟从库,可根据自己需要修改 # 模拟从库,可根据自己需要修改
name: ruoyi-vue-pro
url: jdbc:mysql://47.97.8.94:3306/${spring.datasource.dynamic.datasource.master.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
url: jdbc:mysql://rm-bp1yloyj508qld78jno.mysql.rds.aliyuncs.com:3306/${spring.datasource.dynamic.datasource.slave.name}?allowMultiQueries=true&useUnicode=true&useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&autoReconnect=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例
driver-class-name: com.mysql.jdbc.Driver
username: root
password: yhtkj@2024!
password: Znalyrds2024
# Redis 配置。Redisson 默认的配置足够使用,一般不需要进行调优
redis: