新增管理员查询BD团队概览接口。

This commit is contained in:
tianfeng 2025-11-27 19:09:03 +08:00
parent 2678d8d280
commit 216ba621bf
10 changed files with 660 additions and 0 deletions

View File

@ -0,0 +1,34 @@
package com.red.circle.other.adapter.app;
import com.red.circle.other.app.dto.clientobject.BdTeamOverviewCO;
import com.red.circle.other.app.dto.cmd.BdTeamQueryCmd;
import com.red.circle.other.app.service.BdTeamService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* BD团队控制器
*/
@RestController
@RequestMapping("/bd/team")
@RequiredArgsConstructor
public class BdTeamRestController {
private final BdTeamService bdTeamService;
/**
* 查询BD团队概览
* BD Leader Teams列表
* BD Teams
* 查询Agency Teams
* </p>
*/
@PostMapping("/overview")
public BdTeamOverviewCO queryTeamOverview(@RequestBody BdTeamQueryCmd cmd) {
BdTeamOverviewCO result = bdTeamService.queryTeamOverview(cmd);
return result;
}
}

View File

@ -0,0 +1,392 @@
package com.red.circle.other.app.command.bd.query;
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
import com.red.circle.other.app.dto.clientobject.BdTeamMemberCO;
import com.red.circle.other.app.dto.clientobject.BdTeamOverviewCO;
import com.red.circle.other.app.dto.cmd.BdTeamQueryCmd;
import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.infra.database.mongo.dto.team.TeamBillMemberTarget;
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamBillCycle;
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamMemberTarget;
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamPolicyManager;
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamProfile;
import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamBillCycleService;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamMemberTargetService;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamPolicyManagerService;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamProfileService;
import com.red.circle.other.infra.database.rds.entity.team.BusinessDevelopmentBaseInfo;
import com.red.circle.other.infra.database.rds.entity.team.BusinessDevelopmentTeam;
import com.red.circle.other.infra.database.rds.entity.team.RoomBdLead;
import com.red.circle.other.infra.database.rds.service.team.BusinessDevelopmentBaseInfoService;
import com.red.circle.other.infra.database.rds.service.team.BusinessDevelopmentTeamService;
import com.red.circle.other.infra.database.rds.service.team.RoomBdLeadService;
import com.red.circle.other.inner.enums.team.TeamBillCycleStatus;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
import com.alibaba.fastjson.JSON;
import com.red.circle.component.redis.service.RedisService;
import com.red.circle.tool.core.collection.CollectionUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* BD团队查询执行器
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class BdTeamQryExe {
private final BusinessDevelopmentTeamService businessDevelopmentTeamService;
private final BusinessDevelopmentBaseInfoService businessDevelopmentBaseInfoService;
private final TeamBillCycleService teamBillCycleService;
private final TeamProfileService teamProfileService;
private final TeamMemberTargetService teamMemberTargetService;
private final TeamPolicyManagerService teamPolicyManagerService;
private final UserProfileGateway userProfileGateway;
private final UserProfileAppConvertor userProfileAppConvertor;
private final RoomBdLeadService roomBdLeadService;
private final RedisService redisService;
/**
* 执行查询
*/
public BdTeamOverviewCO execute(BdTeamQueryCmd cmd) {
Long currentUserId = cmd.getReqUserId();
String type = cmd.getType();
// 缓存查询
String cacheKey = "bdteam:" + type + ":"+currentUserId;
BdTeamOverviewCO cached = redisService.getStringToObject(cacheKey, BdTeamOverviewCO.class);
if (cached != null) {
return cached;
}
// 查询数据
BdTeamOverviewCO result = queryTeamList(currentUserId, type);
// 缓存结果
redisService.setString(cacheKey, JSON.toJSONString(result), 2, TimeUnit.MINUTES);
return result;
}
/**
* 查询团队列表
*/
private BdTeamOverviewCO queryTeamList(Long userId, String type) {
BdTeamOverviewCO overview = new BdTeamOverviewCO();
// 获取当前账单周期
Integer currentBillBelong = TeamBillCycleUtils.getCalcBillBelong();
List<BdTeamMemberCO> members;
switch (type) {
case "BD_LEADER":
// BD Leader 视角查询下级 BD 列表
members = queryBdLeaderList(userId, currentBillBelong);
break;
case "BD":
// BD 视角查询下级 Agency 列表
members = queryBdList(userId, currentBillBelong);
break;
case "AGENCY":
// Agency 视角查询下级 Host 列表暂未实现
members = queryAgencyList(userId, currentBillBelong);
break;
default:
members = new ArrayList<>();
}
overview.setMembers(members);
overview.setMemberCount(members.size());
// 计算团队总收入
BigDecimal teamTotal = members.stream()
.map(BdTeamMemberCO::getTotalIncome)
.reduce(BigDecimal.ZERO, BigDecimal::add);
overview.setTeamTotalIncome(teamTotal);
// TODO: 查询当前用户的收入汇总从历史结算记录查询
overview.setTotalIncome(BigDecimal.valueOf(1000));
overview.setPreviousPeriodIncome(BigDecimal.valueOf(1000));
return overview;
}
/**
* 查询 BD 列表BD Leader 视角
* 汇总每个 BD 下所有 Agency 团队的数据
*/
private List<BdTeamMemberCO> queryBdLeaderList(Long bdLeaderUserId, Integer currentBillBelong) {
// 1. 查询该 Leader 下的所有 BD
List<BusinessDevelopmentBaseInfo> bdList =
businessDevelopmentBaseInfoService.listBdByLeadUserId(bdLeaderUserId);
if (CollectionUtils.isEmpty(bdList)) {
return Collections.emptyList();
}
Set<Long> bdIdSet = bdList.stream().map(BusinessDevelopmentBaseInfo::getUserId).collect(Collectors.toSet());
List<RoomBdLead> roomBdLeads = roomBdLeadService.listByUserIds(bdIdSet);
if (CollectionUtils.isEmpty(roomBdLeads)) {
return new ArrayList<>();
}
List<Long> bdLeaderIdList = roomBdLeads.stream().map(RoomBdLead::getUserId).toList();
bdList = bdList.stream()
.filter(e -> !e.getUserId().equals(bdLeaderUserId))
.filter(e -> bdLeaderIdList.contains(e.getUserId())).collect(Collectors.toList());
// 2. 收集所有 BD 的用户ID
Set<Long> bdUserIdSet = bdList.stream()
.map(BusinessDevelopmentBaseInfo::getUserId)
.collect(Collectors.toSet());
// 3. 批量查询所有 BD 的用户信息
Map<Long, UserProfileDTO> bdUserProfileMap = userProfileAppConvertor.toMapUserProfileDTO(
userProfileGateway.mapByUserIds(bdUserIdSet));
// 4. 为每个 BD 构建汇总数据
List<BdTeamMemberCO> result = new ArrayList<>();
for (BusinessDevelopmentBaseInfo bdInfo : bdList) {
BdTeamMemberCO member = buildBdSummary(bdInfo, bdUserProfileMap, currentBillBelong);
result.add(member);
}
return result;
}
/**
* 查询 BD 列表BD Leader 视角
* 汇总每个 BD 下所有 Agency 团队的数据
*/
private List<BdTeamMemberCO> queryBdList(Long bdLeaderUserId, Integer currentBillBelong) {
// 1. 查询该 Leader 下的所有 BD
List<BusinessDevelopmentBaseInfo> bdList =
businessDevelopmentBaseInfoService.listBdByLeadUserId(bdLeaderUserId);
if (CollectionUtils.isEmpty(bdList)) {
return Collections.emptyList();
}
// 2. 收集所有 BD 的用户ID
Set<Long> bdUserIdSet = bdList.stream()
.map(BusinessDevelopmentBaseInfo::getUserId)
.collect(Collectors.toSet());
// 3. 批量查询所有 BD 的用户信息
Map<Long, UserProfileDTO> bdUserProfileMap = userProfileAppConvertor.toMapUserProfileDTO(
userProfileGateway.mapByUserIds(bdUserIdSet));
// 4. 为每个 BD 构建汇总数据
List<BdTeamMemberCO> result = new ArrayList<>();
for (BusinessDevelopmentBaseInfo bdInfo : bdList) {
BdTeamMemberCO member = buildBdSummary(bdInfo, bdUserProfileMap, currentBillBelong);
result.add(member);
}
return result;
}
/**
* 查询 Agency 列表BD 视角
*/
private List<BdTeamMemberCO> queryAgencyList(Long bdUserId, Integer currentBillBelong) {
// 1. 查询该 BD 的所有团队Agent 团队
List<BusinessDevelopmentTeam> bdTeamList = businessDevelopmentTeamService.listByUserId(bdUserId);
if (CollectionUtils.isEmpty(bdTeamList)) {
return Collections.emptyList();
}
// 2. 获取团队ID集合
Set<Long> teamIdSet = bdTeamList.stream()
.map(BusinessDevelopmentTeam::getTeamId)
.collect(Collectors.toSet());
// 3. 查询团队Profile信息包含成员数量
Map<Long, TeamProfile> teamProfileMap = teamProfileService.mapProfileByIds(teamIdSet);
// 4. 查询账单数据
Map<Long, TeamBillCycle> billCycleMap = teamBillCycleService.mapByTeamIds2(
teamIdSet, currentBillBelong, TeamBillCycleStatus.UNPAID);
Map<Long, List<TeamMemberTarget>> memberTargetMap =
teamMemberTargetService.mapMemberBillTarget(teamIdSet, currentBillBelong);
// 5. 查询区域政策
Map<String, TeamPolicyManager> policyManagerMap = teamPolicyManagerService.mapRegionRelease(
billCycleMap.values().stream()
.map(TeamBillCycle::getRegion)
.collect(Collectors.toSet()));
// 6. 获取代理用户信息
Set<Long> agentIdSet = bdTeamList.stream()
.map(BusinessDevelopmentTeam::getAgentId)
.collect(Collectors.toSet());
Map<Long, UserProfileDTO> userProfileMap = userProfileAppConvertor.toMapUserProfileDTO(
userProfileGateway.mapByUserIds(agentIdSet));
// 7. 组装成员列表
List<BdTeamMemberCO> result = new ArrayList<>();
for (BusinessDevelopmentTeam bdTeam : bdTeamList) {
BdTeamMemberCO member = buildAgencyMember(
bdTeam, teamProfileMap, billCycleMap, memberTargetMap, policyManagerMap, userProfileMap);
result.add(member);
}
return result;
}
/**
* 构建 BD 汇总数据汇总该 BD 下所有团队
*/
private BdTeamMemberCO buildBdSummary(
BusinessDevelopmentBaseInfo bdInfo,
Map<Long, UserProfileDTO> bdUserProfileMap,
Integer currentBillBelong) {
BdTeamMemberCO member = new BdTeamMemberCO();
// 设置 BD 用户信息
UserProfileDTO bdUserProfile = bdUserProfileMap.get(bdInfo.getUserId());
if (bdUserProfile != null) {
member.setUserId(bdUserProfile.getId());
member.setUserName(bdUserProfile.getUserNickname());
member.setAvatar(bdUserProfile.getUserAvatar());
member.setAccount(bdUserProfile.getAccount());
}
// 查询该 BD 的所有团队
List<BusinessDevelopmentTeam> bdTeamList =
businessDevelopmentTeamService.listByUserId(bdInfo.getUserId());
// 设置 Agency 数量 BD 管理的 Agent 团队数量
member.setSubMemberCount(CollectionUtils.isEmpty(bdTeamList) ? 0 : bdTeamList.size());
if (CollectionUtils.isEmpty(bdTeamList)) {
member.setTotalIncome(BigDecimal.ZERO);
return member;
}
// 汇总所有团队的总收入真实计算逻辑
BigDecimal totalIncome = calculateBdTotalIncome(bdTeamList, currentBillBelong);
member.setTotalIncome(totalIncome);
return member;
}
/**
* 构建 Agency 成员数据
*/
private BdTeamMemberCO buildAgencyMember(
BusinessDevelopmentTeam bdTeam,
Map<Long, TeamProfile> teamProfileMap,
Map<Long, TeamBillCycle> billCycleMap,
Map<Long, List<TeamMemberTarget>> memberTargetMap,
Map<String, TeamPolicyManager> policyManagerMap,
Map<Long, UserProfileDTO> userProfileMap) {
BdTeamMemberCO member = new BdTeamMemberCO();
// 设置 Agency 用户信息
UserProfileDTO userProfile = userProfileMap.get(bdTeam.getAgentId());
if (userProfile != null) {
member.setUserId(userProfile.getId());
member.setUserName(userProfile.getUserNickname());
member.setAvatar(userProfile.getUserAvatar());
}
// 设置 Host 数量团队成员数量
TeamProfile teamProfile = teamProfileMap.get(bdTeam.getTeamId());
if (teamProfile != null && teamProfile.getCounter() != null) {
Long memberCount = Optional.ofNullable(teamProfile.getCounter().getMemberQuantity()).orElse(0L);
member.setSubMemberCount(memberCount.intValue());
} else {
member.setSubMemberCount(0);
}
// 计算团队总收入真实计算逻辑
BigDecimal teamIncome = getTeamTotalSalary(
billCycleMap, memberTargetMap, policyManagerMap, bdTeam.getTeamId());
member.setTotalIncome(teamIncome);
return member;
}
/**
* 计算 BD 的总收入汇总其所有 Agency 团队的收入
*/
private BigDecimal calculateBdTotalIncome(List<BusinessDevelopmentTeam> bdTeamList, Integer currentBillBelong) {
// 1. 获取所有团队ID
Set<Long> teamIdSet = bdTeamList.stream()
.map(BusinessDevelopmentTeam::getTeamId)
.collect(Collectors.toSet());
// 2. 查询账单周期
Map<Long, TeamBillCycle> billCycleMap = teamBillCycleService.mapByTeamIds2(
teamIdSet, currentBillBelong, TeamBillCycleStatus.UNPAID);
// 3. 查询成员目标
Map<Long, List<TeamMemberTarget>> memberTargetMap =
teamMemberTargetService.mapMemberBillTarget(teamIdSet, currentBillBelong);
// 4. 查询区域政策
Map<String, TeamPolicyManager> policyManagerMap = teamPolicyManagerService.mapRegionRelease(
billCycleMap.values().stream()
.map(TeamBillCycle::getRegion)
.collect(Collectors.toSet()));
// 5. 计算每个团队的收入并汇总
BigDecimal totalIncome = BigDecimal.ZERO;
for (BusinessDevelopmentTeam bdTeam : bdTeamList) {
Long teamId = bdTeam.getTeamId();
BigDecimal teamIncome = getTeamTotalSalary(billCycleMap, memberTargetMap, policyManagerMap, teamId);
totalIncome = totalIncome.add(teamIncome);
}
return totalIncome;
}
/**
* 计算单个团队的总收入复用 TeamBdMemberBillListQryExe 的逻辑
*/
private BigDecimal getTeamTotalSalary(
Map<Long, TeamBillCycle> billCycleMap,
Map<Long, List<TeamMemberTarget>> teamMemberTargetMap,
Map<String, TeamPolicyManager> policyManagerMap,
Long teamId) {
TeamBillCycle billCycle = billCycleMap.get(teamId);
if (billCycle == null) {
return BigDecimal.ZERO;
}
List<TeamMemberTarget> targetList = teamMemberTargetMap.get(teamId);
List<TeamBillMemberTarget> memberTargets = TeamBillCycleUtils.calculatorPolicyTarget(
targetList, policyManagerMap.get(billCycle.getRegion()));
return ownTotalSalary(memberTargets);
}
/**
* 汇总成员工资
*/
private BigDecimal ownTotalSalary(List<TeamBillMemberTarget> teamBillMemberTargets) {
return teamBillMemberTargets.stream()
.map(TeamBillMemberTarget::getTotalSalary)
.reduce(BigDecimal.ZERO, BigDecimal::add)
.setScale(2, RoundingMode.DOWN);
}
}

View File

@ -0,0 +1,24 @@
package com.red.circle.other.app.service;
import com.red.circle.other.app.command.bd.query.BdTeamQryExe;
import com.red.circle.other.app.dto.clientobject.BdTeamOverviewCO;
import com.red.circle.other.app.dto.cmd.BdTeamQueryCmd;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* BD团队服务实现
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class BdTeamServiceImpl implements BdTeamService {
private final BdTeamQryExe bdTeamQryExe;
@Override
public BdTeamOverviewCO queryTeamOverview(BdTeamQueryCmd cmd) {
return bdTeamQryExe.execute(cmd);
}
}

View File

@ -0,0 +1,44 @@
package com.red.circle.other.app.dto.clientobject;
import lombok.Data;
import java.math.BigDecimal;
/**
* BD收入明细
* <p>
* 按半月周期统计的收入信息
* 上半月1-15日
* 下半月16-31日
* </p>
*/
@Data
public class BdIncomeDetailCO {
/**
* 结算周期
* 格式yyyy.MM.dd-yyyy.MM.dd
* 示例2025.10.01-2025.10.15
*/
private String settlementPeriod;
/**
* 周期状态
* In Progress: 进行中
* Completed: 已完成
*/
private String status;
/**
* 下级成员数量
* BD_LEADER: Agency数量
* BD: Host数量
* AGENCY: Host数量
*/
private Integer memberCount;
/**
* 团队总收入
*/
private BigDecimal teamTotalIncome;
}

View File

@ -0,0 +1,53 @@
package com.red.circle.other.app.dto.clientobject;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* BD团队成员
* <p>
* 表示团队列表中的一个成员BD/Agency/Host
* 包含基本信息和收入汇总
* </p>
*/
@Data
public class BdTeamMemberCO {
/**
* 用户ID
*/
private Long userId;
/**
* 用户名
*/
private String userName;
private String account;
/**
* 头像
*/
private String avatar;
/**
* 下级数量
* BD_LEADER视角Agency数量
* BD视角Host数量
* AGENCY视角Host数量
*/
private Integer subMemberCount;
/**
* 总收入
*/
private BigDecimal totalIncome;
/**
* 收入明细列表点击More后显示
* 按半月周期分组
*/
private List<BdIncomeDetailCO> incomeDetails;
}

View File

@ -0,0 +1,46 @@
package com.red.circle.other.app.dto.clientobject;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* BD团队概览
* <p>
* 包含用户自己的收入汇总和团队成员列表
* 适用于三种角色类型BD_LEADERBDAGENCY
* </p>
*/
@Data
public class BdTeamOverviewCO {
/**
* 当前用户总收入
*/
private BigDecimal totalIncome;
/**
* 上期收入
*/
private BigDecimal previousPeriodIncome;
/**
* 团队成员数量
* BD_LEADER: BD数量
* BD: Agency数量
* AGENCY: Host数量
*/
private Integer memberCount;
/**
* 团队总收入所有下级的收入总和
*/
private BigDecimal teamTotalIncome;
/**
* 团队成员列表
*/
private List<BdTeamMemberCO> members;
}

View File

@ -0,0 +1,28 @@
package com.red.circle.other.app.dto.cmd;
import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* BD团队查询命令
* <p>
* 用于查询BD LeaderBDAgency的团队列表和收入详情
* 支持列表查询和单个成员详情查询
* </p>
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BdTeamQueryCmd extends AppExtCommand {
/**
* 团队类型
* BD_LEADER: BD Leader团队查看下级BD
* BD: BD团队查看下级Agency
* AGENCY: Agency团队查看下级Host
*/
@NotNull(message = "团队类型不能为空")
private String type;
}

View File

@ -0,0 +1,23 @@
package com.red.circle.other.app.service;
import com.red.circle.other.app.dto.clientobject.BdTeamOverviewCO;
import com.red.circle.other.app.dto.cmd.BdTeamQueryCmd;
/**
* BD团队服务
*/
public interface BdTeamService {
/**
* 查询BD团队概览
* <p>
* 支持三种场景
* 1. 列表查询不传targetUserId返回当前用户的团队成员列表
* 2. 详情查询传targetUserId返回指定成员的收入明细
* </p>
*
* @param cmd 查询命令
* @return 团队概览信息
*/
BdTeamOverviewCO queryTeamOverview(BdTeamQueryCmd cmd);
}

View File

@ -3,6 +3,9 @@ package com.red.circle.other.infra.database.rds.service.team;
import com.red.circle.framework.mybatis.service.BaseService;
import com.red.circle.other.infra.database.rds.entity.team.RoomBdLead;
import java.util.List;
import java.util.Set;
/**
* <p>
* BD Lead.
@ -19,4 +22,6 @@ public interface RoomBdLeadService extends BaseService<RoomBdLead> {
void deleteById(Long id);
List<RoomBdLead> listByUserIds(Set<Long> userIds);
}

View File

@ -5,7 +5,11 @@ import com.red.circle.framework.mybatis.service.impl.BaseServiceImpl;
import com.red.circle.other.infra.database.rds.dao.team.RoomBdLeadDAO;
import com.red.circle.other.infra.database.rds.entity.team.RoomBdLead;
import com.red.circle.other.infra.database.rds.service.team.RoomBdLeadService;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.springframework.stereotype.Service;
/**
@ -42,4 +46,11 @@ public class RoomBdLeadServiceImpl extends BaseServiceImpl<RoomBdLeadDAO, RoomBd
delete().eq(RoomBdLead::getId, id).execute();
}
@Override
public List<RoomBdLead> listByUserIds(Set<Long> userIds) {
return query()
.in(RoomBdLead::getUserId, userIds)
.list();
}
}