新增BD团队more接口

This commit is contained in:
tianfeng 2025-11-27 20:02:36 +08:00
parent 216ba621bf
commit bb23ff48e3
8 changed files with 499 additions and 3 deletions

View File

@ -1,6 +1,8 @@
package com.red.circle.other.adapter.app;
import com.red.circle.other.app.dto.clientobject.BdTeamMemberDetailCO;
import com.red.circle.other.app.dto.clientobject.BdTeamOverviewCO;
import com.red.circle.other.app.dto.cmd.BdTeamMemberDetailQueryCmd;
import com.red.circle.other.app.dto.cmd.BdTeamQueryCmd;
import com.red.circle.other.app.service.BdTeamService;
import lombok.RequiredArgsConstructor;
@ -21,9 +23,11 @@ public class BdTeamRestController {
/**
* 查询BD团队概览
* BD Leader Teams列表
* BD Teams
* 查询Agency Teams
* <p>
* 统一接口支持三种场景
* 1. 查询BD Leader Teams列表type=BD_LEADER
* 2. 查询BD Teams列表type=BD
* 3. 查询Agency Teams列表type=AGENCY
* </p>
*/
@PostMapping("/overview")
@ -31,4 +35,18 @@ public class BdTeamRestController {
BdTeamOverviewCO result = bdTeamService.queryTeamOverview(cmd);
return result;
}
/**
* 查询BD团队成员详情More弹框
* <p>
* 支持两种场景
* 1. Agency MoretargetId=teamId, type=AGENCY
* 2. BD MoretargetId=bdUserId, type=BD
* </p>
*/
@PostMapping("/member/detail")
public BdTeamMemberDetailCO queryMemberDetail(@RequestBody BdTeamMemberDetailQueryCmd cmd) {
return bdTeamService.queryMemberDetail(cmd);
}
}

View File

@ -0,0 +1,353 @@
package com.red.circle.other.app.command.bd.query;
import com.red.circle.framework.core.asserts.ResponseAssert;
import com.red.circle.other.app.convertor.user.UserProfileAppConvertor;
import com.red.circle.other.app.dto.clientobject.BdIncomeDetailCO;
import com.red.circle.other.app.dto.clientobject.BdIncomeDetailMemberCO;
import com.red.circle.other.app.dto.clientobject.BdTeamMemberDetailCO;
import com.red.circle.other.app.dto.cmd.BdTeamMemberDetailQueryCmd;
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.BusinessDevelopmentTeam;
import com.red.circle.other.infra.database.rds.service.team.BusinessDevelopmentTeamService;
import com.red.circle.other.inner.asserts.team.TeamErrorCode;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
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.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
/**
* BD团队成员详情查询执行器
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class BdTeamMemberDetailQryExe {
private static final ZoneId RIYADH_ZONE = ZoneId.of("Asia/Riyadh");
private final TeamBillCycleService teamBillCycleService;
private final TeamMemberTargetService teamMemberTargetService;
private final TeamPolicyManagerService teamPolicyManagerService;
private final BusinessDevelopmentTeamService businessDevelopmentTeamService;
private final UserProfileGateway userProfileGateway;
private final UserProfileAppConvertor userProfileAppConvertor;
private final TeamProfileService teamProfileService;
/**
* 执行查询
*/
public BdTeamMemberDetailCO execute(BdTeamMemberDetailQueryCmd cmd) {
String type = cmd.getType();
Long targetId = cmd.getTargetId();
if ("AGENCY".equals(type)) {
// Agency视角查询单个team的数据
return queryAgencyDetail(targetId);
} else if ("BD".equals(type)) {
// BD视角查询该BD下所有team的汇总数据
return queryBdDetail(targetId);
}
return new BdTeamMemberDetailCO();
}
/**
* 查询Agency详情单个team
*/
private BdTeamMemberDetailCO queryAgencyDetail(Long userId) {
BdTeamMemberDetailCO detail = new BdTeamMemberDetailCO();
TeamProfile teamProfile = teamProfileService.getByOwnUserId(userId);
ResponseAssert.notNull(TeamErrorCode.NOT_TEAM_MEMBER, teamProfile);
// 查询最近2个账单周期
List<TeamBillCycle> teamBillCycles = teamBillCycleService.listLatestBill(teamProfile.getId(), 2);
if (CollectionUtils.isEmpty(teamBillCycles)) {
detail.setTotalIncome(BigDecimal.ZERO);
detail.setPreviousPeriodIncome(BigDecimal.ZERO);
detail.setIncomeDetails(Collections.emptyList());
return detail;
}
// 获取所有区域的政策配置
Map<String, TeamPolicyManager> regionPolicyMap = teamPolicyManagerService.mapRegionRelease(
teamBillCycles.stream().map(TeamBillCycle::getRegion).collect(Collectors.toSet()));
List<BdIncomeDetailCO> incomeDetails = new ArrayList<>();
for (TeamBillCycle teamBillCycle : teamBillCycles) {
if (Objects.isNull(teamBillCycle)) {
continue;
}
// 查询该账单周期下的所有成员目标数据
List<TeamMemberTarget> teamMemberTargets = teamMemberTargetService.listByBillBelong(
teamBillCycle.getTeamId(), teamBillCycle.getBillBelong());
BdIncomeDetailCO incomeDetail = buildIncomeDetail(
teamBillCycle, teamMemberTargets, regionPolicyMap.get(teamBillCycle.getRegion()));
incomeDetails.add(incomeDetail);
}
// 计算总收入和上期收入
detail.setIncomeDetails(incomeDetails);
if (incomeDetails.size() >= 1) {
detail.setTotalIncome(incomeDetails.get(0).getTeamTotalIncome());
} else {
detail.setTotalIncome(BigDecimal.ZERO);
}
if (incomeDetails.size() >= 2) {
detail.setPreviousPeriodIncome(incomeDetails.get(1).getTeamTotalIncome());
} else {
detail.setPreviousPeriodIncome(BigDecimal.ZERO);
}
return detail;
}
/**
* 查询BD详情多个team汇总
*/
private BdTeamMemberDetailCO queryBdDetail(Long bdUserId) {
BdTeamMemberDetailCO detail = new BdTeamMemberDetailCO();
// 查询该BD的所有团队
List<BusinessDevelopmentTeam> bdTeamList = businessDevelopmentTeamService.listByUserId(bdUserId);
if (CollectionUtils.isEmpty(bdTeamList)) {
detail.setTotalIncome(BigDecimal.ZERO);
detail.setPreviousPeriodIncome(BigDecimal.ZERO);
detail.setIncomeDetails(Collections.emptyList());
return detail;
}
Set<Long> teamIdSet = bdTeamList.stream()
.map(BusinessDevelopmentTeam::getTeamId)
.collect(Collectors.toSet());
// 为每个team查询最近2个账单周期
Map<Long, List<TeamBillCycle>> teamBillMap = new HashMap<>();
for (Long teamId : teamIdSet) {
List<TeamBillCycle> bills = teamBillCycleService.listLatestBill(teamId, 2);
if (CollectionUtils.isNotEmpty(bills)) {
teamBillMap.put(teamId, bills);
}
}
// 按周期分组汇总
Map<Integer, List<TeamBillCycle>> billBelongMap = new HashMap<>();
for (List<TeamBillCycle> bills : teamBillMap.values()) {
for (TeamBillCycle bill : bills) {
billBelongMap.computeIfAbsent(bill.getBillBelong(), k -> new ArrayList<>()).add(bill);
}
}
// 获取所有区域的政策配置
Set<String> allRegions = teamBillMap.values().stream()
.flatMap(List::stream)
.map(TeamBillCycle::getRegion)
.collect(Collectors.toSet());
Map<String, TeamPolicyManager> regionPolicyMap = teamPolicyManagerService.mapRegionRelease(allRegions);
// 按周期构建收入明细
List<BdIncomeDetailCO> incomeDetails = new ArrayList<>();
List<Integer> sortedBillBelongs = billBelongMap.keySet().stream()
.sorted(Comparator.reverseOrder())
.toList();
for (Integer billBelong : sortedBillBelongs) {
List<TeamBillCycle> billsInPeriod = billBelongMap.get(billBelong);
BdIncomeDetailCO incomeDetail = buildBdIncomeDetailForPeriod(
billBelong, billsInPeriod, regionPolicyMap, bdTeamList);
incomeDetails.add(incomeDetail);
}
// 计算总收入和上期收入
detail.setIncomeDetails(incomeDetails);
if (incomeDetails.size() >= 1) {
detail.setTotalIncome(incomeDetails.get(0).getTeamTotalIncome());
} else {
detail.setTotalIncome(BigDecimal.ZERO);
}
if (incomeDetails.size() >= 2) {
detail.setPreviousPeriodIncome(incomeDetails.get(1).getTeamTotalIncome());
} else {
detail.setPreviousPeriodIncome(BigDecimal.ZERO);
}
return detail;
}
/**
* 构建单个周期的收入明细Agency视角
*/
private BdIncomeDetailCO buildIncomeDetail(
TeamBillCycle teamBillCycle,
List<TeamMemberTarget> teamMemberTargets,
TeamPolicyManager policy) {
BdIncomeDetailCO incomeDetail = new BdIncomeDetailCO();
// 设置周期信息
String[] dates = TeamBillCycleUtils.parseBillBelongToDateRange(teamBillCycle.getBillBelong());
incomeDetail.setSettlementPeriod(dates[0] + "-" + dates[1]);
// 判断状态
LocalDateTime now = LocalDateTime.now(RIYADH_ZONE);
LocalDateTime endTime = TeamBillCycleUtils.getBillBelongEndTime(teamBillCycle.getBillBelong());
incomeDetail.setStatus(now.isAfter(endTime) ? "Completed" : "In Progress");
if (CollectionUtils.isEmpty(teamMemberTargets)) {
incomeDetail.setMemberCount(0);
incomeDetail.setTeamTotalIncome(BigDecimal.ZERO);
incomeDetail.setMembers(Collections.emptyList());
return incomeDetail;
}
// 计算每个成员的政策目标数据
List<TeamBillMemberTarget> memberTargetList = new ArrayList<>();
for (TeamMemberTarget target : teamMemberTargets) {
TeamBillMemberTarget teamBillMemberTarget = TeamBillCycleUtils.calculatorPolicyTarget(target, policy);
memberTargetList.add(teamBillMemberTarget);
}
// 设置成员数量和总收入
incomeDetail.setMemberCount(memberTargetList.size());
BigDecimal totalIncome = memberTargetList.stream()
.map(TeamBillMemberTarget::getTotalSalary)
.reduce(BigDecimal.ZERO, BigDecimal::add);
incomeDetail.setTeamTotalIncome(totalIncome);
// 构建成员详细列表
Set<Long> memberIds = memberTargetList.stream()
.map(TeamBillMemberTarget::getUserId)
.collect(Collectors.toSet());
Map<Long, UserProfileDTO> userProfileMap = userProfileAppConvertor.toMapUserProfileDTO(
userProfileGateway.mapByUserIds(memberIds));
List<BdIncomeDetailMemberCO> members = new ArrayList<>();
for (TeamBillMemberTarget memberTarget : memberTargetList) {
BdIncomeDetailMemberCO member = new BdIncomeDetailMemberCO();
UserProfileDTO userProfile = userProfileMap.get(memberTarget.getUserId());
if (userProfile != null) {
member.setUserId(userProfile.getId());
member.setUserName(userProfile.getUserNickname());
member.setAvatar(userProfile.getUserAvatar());
member.setAccount(userProfile.getAccount());
}
member.setIncome(memberTarget.getTotalSalary());
members.add(member);
}
incomeDetail.setMembers(members);
return incomeDetail;
}
/**
* 构建BD某个周期的收入明细汇总多个team
*/
private BdIncomeDetailCO buildBdIncomeDetailForPeriod(
Integer billBelong,
List<TeamBillCycle> billsInPeriod,
Map<String, TeamPolicyManager> regionPolicyMap,
List<BusinessDevelopmentTeam> bdTeamList) {
BdIncomeDetailCO incomeDetail = new BdIncomeDetailCO();
// 设置周期信息
String[] dates = TeamBillCycleUtils.parseBillBelongToDateRange(billBelong);
incomeDetail.setSettlementPeriod(dates[0] + "-" + dates[1]);
// 判断状态
LocalDateTime now = LocalDateTime.now(RIYADH_ZONE);
LocalDateTime endTime = TeamBillCycleUtils.getBillBelongEndTime(billBelong);
incomeDetail.setStatus(now.isAfter(endTime) ? "Completed" : "In Progress");
// 设置Agency数量
incomeDetail.setMemberCount(billsInPeriod.size());
// 汇总所有team的收入
BigDecimal totalIncome = BigDecimal.ZERO;
List<BdIncomeDetailMemberCO> members = new ArrayList<>();
// 获取所有Agency的用户信息
Map<Long, Long> teamIdToAgentIdMap = bdTeamList.stream()
.collect(Collectors.toMap(
BusinessDevelopmentTeam::getTeamId,
BusinessDevelopmentTeam::getAgentId,
(existing, replacement) -> existing
));
Set<Long> agentIds = new HashSet<>(teamIdToAgentIdMap.values());
Map<Long, UserProfileDTO> userProfileMap = userProfileAppConvertor.toMapUserProfileDTO(
userProfileGateway.mapByUserIds(agentIds));
for (TeamBillCycle bill : billsInPeriod) {
// 查询该team的成员目标
List<TeamMemberTarget> teamMemberTargets = teamMemberTargetService.listByBillBelong(
bill.getTeamId(), bill.getBillBelong());
if (CollectionUtils.isEmpty(teamMemberTargets)) {
continue;
}
// 计算team收入
TeamPolicyManager policy = regionPolicyMap.get(bill.getRegion());
List<TeamBillMemberTarget> memberTargetList = new ArrayList<>();
for (TeamMemberTarget target : teamMemberTargets) {
TeamBillMemberTarget teamBillMemberTarget = TeamBillCycleUtils.calculatorPolicyTarget(target, policy);
memberTargetList.add(teamBillMemberTarget);
}
BigDecimal teamIncome = memberTargetList.stream()
.map(TeamBillMemberTarget::getTotalSalary)
.reduce(BigDecimal.ZERO, BigDecimal::add);
totalIncome = totalIncome.add(teamIncome);
// 构建Agency成员信息
Long agentId = teamIdToAgentIdMap.get(bill.getTeamId());
if (agentId != null) {
BdIncomeDetailMemberCO member = new BdIncomeDetailMemberCO();
UserProfileDTO userProfile = userProfileMap.get(agentId);
if (userProfile != null) {
member.setUserId(userProfile.getId());
member.setUserName(userProfile.getUserNickname());
member.setAvatar(userProfile.getUserAvatar());
member.setAccount(userProfile.getAccount());
}
member.setIncome(teamIncome);
members.add(member);
}
}
incomeDetail.setTeamTotalIncome(totalIncome);
incomeDetail.setMembers(members);
return incomeDetail;
}
}

View File

@ -1,7 +1,10 @@
package com.red.circle.other.app.service;
import com.red.circle.other.app.command.bd.query.BdTeamMemberDetailQryExe;
import com.red.circle.other.app.command.bd.query.BdTeamQryExe;
import com.red.circle.other.app.dto.clientobject.BdTeamMemberDetailCO;
import com.red.circle.other.app.dto.clientobject.BdTeamOverviewCO;
import com.red.circle.other.app.dto.cmd.BdTeamMemberDetailQueryCmd;
import com.red.circle.other.app.dto.cmd.BdTeamQueryCmd;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -16,9 +19,15 @@ import org.springframework.stereotype.Service;
public class BdTeamServiceImpl implements BdTeamService {
private final BdTeamQryExe bdTeamQryExe;
private final BdTeamMemberDetailQryExe bdTeamMemberDetailQryExe;
@Override
public BdTeamOverviewCO queryTeamOverview(BdTeamQueryCmd cmd) {
return bdTeamQryExe.execute(cmd);
}
@Override
public BdTeamMemberDetailCO queryMemberDetail(BdTeamMemberDetailQueryCmd cmd) {
return bdTeamMemberDetailQryExe.execute(cmd);
}
}

View File

@ -3,6 +3,7 @@ package com.red.circle.other.app.dto.clientobject;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* BD收入明细
@ -41,4 +42,9 @@ public class BdIncomeDetailCO {
* 团队总收入
*/
private BigDecimal teamTotalIncome;
/**
* 成员详细列表点击More后展开
*/
private List<BdIncomeDetailMemberCO> members;
}

View File

@ -0,0 +1,39 @@
package com.red.circle.other.app.dto.clientobject;
import lombok.Data;
import java.math.BigDecimal;
/**
* 收入明细成员
*/
@Data
public class BdIncomeDetailMemberCO {
/**
* 用户ID
*/
private Long userId;
/**
* 用户名
*/
private String userName;
/**
* 头像
*/
private String avatar;
/**
* 账号ID
*/
private String account;
/**
* 收入金额
* Agency视角Host收入
* BD视角Agency团队收入
*/
private BigDecimal income;
}

View File

@ -0,0 +1,28 @@
package com.red.circle.other.app.dto.clientobject;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
/**
* BD团队成员详情
*/
@Data
public class BdTeamMemberDetailCO {
/**
* 总收入
*/
private BigDecimal totalIncome;
/**
* 上期收入
*/
private BigDecimal previousPeriodIncome;
/**
* 收入明细列表按半月周期
*/
private List<BdIncomeDetailCO> incomeDetails;
}

View File

@ -0,0 +1,30 @@
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团队成员详情查询命令
*/
@Data
@EqualsAndHashCode(callSuper = true)
public class BdTeamMemberDetailQueryCmd extends AppExtCommand {
/**
* 目标ID
* type=AGENCY: 传userId
* type=BD: 传bdUserId
*/
@NotNull(message = "目标ID不能为空")
private Long targetId;
/**
* 类型
* AGENCY: 查询Agency下的Host列表
* BD: 查询BD下的所有Agency团队数据
*/
@NotNull(message = "类型不能为空")
private String type;
}

View File

@ -1,6 +1,8 @@
package com.red.circle.other.app.service;
import com.red.circle.other.app.dto.clientobject.BdTeamMemberDetailCO;
import com.red.circle.other.app.dto.clientobject.BdTeamOverviewCO;
import com.red.circle.other.app.dto.cmd.BdTeamMemberDetailQueryCmd;
import com.red.circle.other.app.dto.cmd.BdTeamQueryCmd;
/**
@ -20,4 +22,15 @@ public interface BdTeamService {
* @return 团队概览信息
*/
BdTeamOverviewCO queryTeamOverview(BdTeamQueryCmd cmd);
/**
* 查询BD团队成员详情More弹框
* <p>
* 返回按半月周期分组的收入明细包括成员列表
* </p>
*
* @param cmd 查询命令
* @return 成员详情
*/
BdTeamMemberDetailCO queryMemberDetail(BdTeamMemberDetailQueryCmd cmd);
}