自动结算

This commit is contained in:
hy001 2026-06-01 19:46:48 +08:00
parent 6573219751
commit 85f3ffbc10
14 changed files with 613 additions and 250 deletions

View File

@ -28,6 +28,11 @@ public class TeamManualSalaryPaymentCmd extends Command {
*/ */
private String countryCode; private String countryCode;
/**
* 工资账期为空时使用当前整月账期.
*/
private Integer billBelong;
/** /**
* 指定发放用户为空时按国家全量发放. * 指定发放用户为空时按国家全量发放.
*/ */

View File

@ -27,6 +27,11 @@ public class TeamManualSalaryPaymentQryCmd extends PageCommand {
*/ */
private String countryCode; private String countryCode;
/**
* 工资账期为空时使用当前整月账期.
*/
private Integer billBelong;
/** /**
* 用户 id. * 用户 id.
*/ */

View File

@ -146,6 +146,11 @@ public class TeamManualSalaryPaymentDTO implements Serializable {
*/ */
private BigDecimal currentSalaryBalance; private BigDecimal currentSalaryBalance;
/**
* 当前用户是否为团队代理本人.
*/
private Boolean agentMember;
/** /**
* 当前档位是否已发放. * 当前档位是否已发放.
*/ */

View File

@ -228,6 +228,7 @@ public class TeamSalaryBackServiceImpl implements TeamSalaryBackService {
.setMemberOverIssuedSalary(dto.getMemberOverIssuedSalary()) .setMemberOverIssuedSalary(dto.getMemberOverIssuedSalary())
.setAgentOverIssuedSalary(dto.getAgentOverIssuedSalary()) .setAgentOverIssuedSalary(dto.getAgentOverIssuedSalary())
.setCurrentSalaryBalance(dto.getCurrentSalaryBalance()) .setCurrentSalaryBalance(dto.getCurrentSalaryBalance())
.setAgentMember(dto.getAgentMember())
.setPaid(dto.getPaid()); .setPaid(dto.getPaid());
} }

View File

@ -30,6 +30,7 @@ import com.red.circle.tool.core.sequence.IdWorkerUtils;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.time.Duration; import java.time.Duration;
import java.util.HashSet;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -96,7 +97,10 @@ public class GmVipGiftServiceImpl implements GmVipGiftService {
expireAt, now); expireAt, now);
vipOrderRecordService.save(order); vipOrderRecordService.save(order);
Set<Long> previousResourceIds = vipResourceIds(currentState);
upsertUserVipState(currentState, userProfile, config, startAt, expireAt, now); upsertUserVipState(currentState, userProfile, config, startAt, expireAt, now);
expireSupersededVipResources(userProfile.getOriginSys(), userProfile.getId(), config,
previousResourceIds, now);
giftVipResources(userProfile.getId(), config, now, cmd.getReqUserId()); giftVipResources(userProfile.getId(), config, now, cmd.getReqUserId());
GmVipGiftRecord record = buildGiftRecord(cmd, applicant, userProfile, fromLevel, config, GmVipGiftRecord record = buildGiftRecord(cmd, applicant, userProfile, fromLevel, config,
@ -272,6 +276,63 @@ public class GmVipGiftServiceImpl implements GmVipGiftService {
now, opUser); now, opUser);
} }
private void expireSupersededVipResources(String sysOrigin, Long userId,
VipLevelConfig targetConfig, Set<Long> previousResourceIds, Timestamp now) {
Set<Long> targetResourceIds = vipResourceIds(targetConfig);
Set<Long> allVipResourceIds = new HashSet<>();
vipLevelConfigService.query()
.eq(VipLevelConfig::getSysOrigin, sysOrigin)
.list()
.forEach(config -> allVipResourceIds.addAll(vipResourceIds(config)));
allVipResourceIds.addAll(previousResourceIds);
allVipResourceIds.removeAll(targetResourceIds);
if (CollectionUtils.isEmpty(allVipResourceIds)) {
return;
}
userPropsBackpackService.update()
.set(UserPropsBackpack::getExpireTime, now)
.set(UserPropsBackpack::getUseProps, Boolean.FALSE)
.set(UserPropsBackpack::getAllowGive, Boolean.FALSE)
.set(UserPropsBackpack::getUpdateTime, now)
.eq(UserPropsBackpack::getUserId, userId)
.in(UserPropsBackpack::getPropsId, allVipResourceIds)
.gt(UserPropsBackpack::getExpireTime, now)
.execute();
}
private Set<Long> vipResourceIds(VipLevelConfig config) {
Set<Long> ids = new HashSet<>();
if (Objects.isNull(config)) {
return ids;
}
addPositive(ids, config.getBadgeResourceId());
addPositive(ids, config.getAvatarFrameResourceId());
addPositive(ids, config.getEntryEffectResourceId());
addPositive(ids, config.getChatBubbleResourceId());
addPositive(ids, config.getFloatPictureResourceId());
return ids;
}
private Set<Long> vipResourceIds(UserVipState state) {
Set<Long> ids = new HashSet<>();
if (Objects.isNull(state)) {
return ids;
}
addPositive(ids, state.getBadgeResourceId());
addPositive(ids, state.getAvatarFrameResourceId());
addPositive(ids, state.getEntryEffectResourceId());
addPositive(ids, state.getChatBubbleResourceId());
addPositive(ids, state.getFloatPictureResourceId());
return ids;
}
private void addPositive(Set<Long> ids, Long id) {
if (Objects.nonNull(id) && id > 0) {
ids.add(id);
}
}
private void giveProp(Long userId, Long propsId, String type, Integer days, Timestamp now, private void giveProp(Long userId, Long propsId, String type, Integer days, Timestamp now,
Long opUser) { Long opUser) {
if (Objects.isNull(propsId) || propsId <= 0 || Objects.isNull(days) || days <= 0) { if (Objects.isNull(propsId) || propsId <= 0 || Objects.isNull(days) || days <= 0) {

View File

@ -52,5 +52,6 @@ public class TeamManualSalaryCO implements Serializable {
private BigDecimal memberOverIssuedSalary; private BigDecimal memberOverIssuedSalary;
private BigDecimal agentOverIssuedSalary; private BigDecimal agentOverIssuedSalary;
private BigDecimal currentSalaryBalance; private BigDecimal currentSalaryBalance;
private Boolean agentMember;
private Boolean paid; private Boolean paid;
} }

View File

@ -132,10 +132,10 @@ public class TestRestController extends BaseController {
teamSalaryPaymentTask.teamSalaryPaymentTaskTest(); teamSalaryPaymentTask.teamSalaryPaymentTaskTest();
} }
@PostMapping("/processTeamMonthBill") @PostMapping("/processTeamMonthBill")
public void processTeamMonthBill() { public void processTeamMonthBill() {
teamBillTask.processTeamBillRetry(); teamBillTask.processTeamMonthBillTest();
} }
@PostMapping("/activity/send/game") @PostMapping("/activity/send/game")
public void activitySendGame(@RequestBody RewardPoolCmd cmd) { public void activitySendGame(@RequestBody RewardPoolCmd cmd) {

View File

@ -2,7 +2,6 @@ package com.red.circle.other.app.command.bd.query;
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSON;
import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; import com.red.circle.common.business.core.enums.SysOriginPlatformEnum;
import com.red.circle.component.redis.RedisKeys;
import com.red.circle.component.redis.service.RedisService; import com.red.circle.component.redis.service.RedisService;
import com.red.circle.framework.dto.ResultResponse; import com.red.circle.framework.dto.ResultResponse;
import com.red.circle.order.inner.endpoint.InAppPurchaseDetailsClient; import com.red.circle.order.inner.endpoint.InAppPurchaseDetailsClient;
@ -17,7 +16,6 @@ import com.red.circle.other.app.service.BdSalarySettlementServiceImpl;
import com.red.circle.other.domain.gateway.user.UserProfileGateway; 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.dto.team.TeamBillMemberTarget;
import com.red.circle.other.infra.database.mongo.entity.bd.BdSalaryPolicy; import com.red.circle.other.infra.database.mongo.entity.bd.BdSalaryPolicy;
import com.red.circle.other.infra.database.mongo.entity.bd.BdSalarySettlementRecord;
import com.red.circle.other.infra.database.mongo.entity.team.team.*; import com.red.circle.other.infra.database.mongo.entity.team.team.*;
import com.red.circle.other.infra.database.mongo.service.bd.BdSalaryPolicyService; import com.red.circle.other.infra.database.mongo.service.bd.BdSalaryPolicyService;
import com.red.circle.other.infra.database.mongo.service.team.team.*; import com.red.circle.other.infra.database.mongo.service.team.team.*;
@ -29,7 +27,6 @@ import com.red.circle.other.infra.database.rds.entity.team.BusinessDevelopmentBa
import com.red.circle.other.infra.database.rds.entity.team.BusinessDevelopmentTeam; import com.red.circle.other.infra.database.rds.entity.team.BusinessDevelopmentTeam;
import com.red.circle.other.infra.database.rds.service.team.BusinessDevelopmentBaseInfoService; 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.BusinessDevelopmentTeamService;
import com.red.circle.other.inner.endpoint.team.bd.BdTeamLeaderClient;
import com.red.circle.other.inner.enums.team.TeamBillCycleStatus; import com.red.circle.other.inner.enums.team.TeamBillCycleStatus;
import com.red.circle.other.inner.model.dto.agency.agency.TeamCounter; import com.red.circle.other.inner.model.dto.agency.agency.TeamCounter;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO; import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
@ -43,8 +40,6 @@ import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -80,8 +75,10 @@ public class TeamBdMemberBillListQryExe {
* @return 账单列表 * @return 账单列表
*/ */
public TeamBdMemberCO execute(TeamBdMemberBillCmd cmd) { public TeamBdMemberCO execute(TeamBdMemberBillCmd cmd) {
Integer billBelong = resolveBillBelong(cmd.getBillBelong());
// 1. 缓存查询 // 1. 缓存查询
String key = "bdcenter:" + cmd.getType() + ":" + cmd.getReqUserId(); String key = "bdcenter:" + cmd.getType() + ":" + cmd.getReqUserId() + ":" + billBelong;
TeamBdMemberCO cached = redisService.getStringToObject(key, TeamBdMemberCO.class); TeamBdMemberCO cached = redisService.getStringToObject(key, TeamBdMemberCO.class);
if (cached != null) { if (cached != null) {
return cached; return cached;
@ -90,45 +87,68 @@ public class TeamBdMemberBillListQryExe {
// 2. 根据类型执行不同的查询逻辑 // 2. 根据类型执行不同的查询逻辑
TeamBdMemberCO result; TeamBdMemberCO result;
if ("BD".equals(cmd.getType())) { if ("BD".equals(cmd.getType())) {
result = queryBdTeamData(cmd.getReqUserId()); result = queryBdTeamData(cmd.getReqUserId(), billBelong);
} else { } else {
result = queryBdLeaderTeamData(cmd.getReqUserId()); result = queryBdLeaderTeamData(cmd.getReqUserId(), billBelong);
} }
// 3. 缓存结果 // 3. 缓存结果
redisService.setString(key, JSON.toJSONString(result), 2, TimeUnit.MINUTES); redisService.setString(key, JSON.toJSONString(result), 2, TimeUnit.MINUTES);
return result; return result;
} }
private Integer resolveBillBelong(Integer billBelong) {
if (billBelong == null) {
return TeamBillCycleUtils.getCalcBillBelong();
}
String value = String.valueOf(billBelong);
if (value.length() == 6) {
int month = Integer.parseInt(value.substring(4, 6));
if (month >= 1 && month <= 12) {
return billBelong;
}
}
if (value.length() == 8) {
try {
LocalDate.of(
Integer.parseInt(value.substring(0, 4)),
Integer.parseInt(value.substring(4, 6)),
Integer.parseInt(value.substring(6, 8)));
return billBelong;
} catch (RuntimeException ignored) {
return TeamBillCycleUtils.getCalcBillBelong();
}
}
return TeamBillCycleUtils.getCalcBillBelong();
}
/** /**
* 查询 BD 的团队数据返回 Agent 团队列表. * 查询 BD 的团队数据返回 Agent 团队列表.
*/ */
private TeamBdMemberCO queryBdTeamData(Long bdUserId) { private TeamBdMemberCO queryBdTeamData(Long bdUserId, Integer currentBillBelong) {
// 1. 查询该 BD 的所有团队 // 1. 查询该 BD 的所有团队
List<BusinessDevelopmentTeam> bdTeamList = businessDevelopmentTeamService.listByUserId(bdUserId); List<BusinessDevelopmentTeam> bdTeamList = businessDevelopmentTeamService.listByUserId(bdUserId);
if (CollectionUtils.isEmpty(bdTeamList)) { if (CollectionUtils.isEmpty(bdTeamList)) {
return createEmptyResult(); return createEmptyResult(currentBillBelong);
} }
// 2. 获取当前账单周期
Integer currentBillBelong = TeamBillCycleUtils.getCalcBillBelong();
// 3. 获取团队ID集合 // 3. 获取团队ID集合
Set<Long> teamIdSet = bdTeamList.stream() Set<Long> teamIdSet = bdTeamList.stream()
.map(BusinessDevelopmentTeam::getTeamId) .map(BusinessDevelopmentTeam::getTeamId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 获取所有区域的政策配置 // 获取所有区域的政策配置
Map<Long, TeamBillCycle> billCycleMap = teamBillCycleService.mapByTeamIds2( Map<Long, TeamBillCycle> billCycleMap = getBillCycleMap(teamIdSet, currentBillBelong);
teamIdSet, currentBillBelong, TeamBillCycleStatus.UNPAID);
Map<String, TeamPolicyManager> regionPolicyMap = teamPolicyManagerService.mapRegionReleaseWithCountry( Map<String, TeamPolicyManager> regionPolicyMap = teamPolicyManagerService.mapRegionReleaseWithCountry(
billCycleMap.values().stream().map(TeamBillCycle::getRegion).collect(Collectors.toSet())); billCycleMap.values().stream().map(TeamBillCycle::getRegion).collect(Collectors.toSet()));
// 4. 查询当前周期的账单 // 4. 查询当前周期的账单
Map<Long, List<TeamMemberTarget>> memberBillTarget = Map<Long, List<TeamMemberTarget>> memberBillTarget = getMemberBillTargetMap(teamIdSet, currentBillBelong);
teamMemberTargetService.mapMemberBillTarget(teamIdSet, currentBillBelong);
// 5. 获取团队Profile信息 // 5. 获取团队Profile信息
Map<Long, TeamProfile> teamProfileMap = teamProfileService.mapProfileByIds(teamIdSet); Map<Long, TeamProfile> teamProfileMap = teamProfileService.mapProfileByIds(teamIdSet);
@ -168,9 +188,9 @@ public class TeamBdMemberBillListQryExe {
result.setTotalSalary(totalSalary); result.setTotalSalary(totalSalary);
result.setTotalRecharge(totalRecharge); result.setTotalRecharge(totalRecharge);
result.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(currentBillBelong)); result.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(currentBillBelong));
// 11. 计算当前账单的 BD 工资信息 // 11. 计算当前账单的 BD 工资信息
BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong, BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong,
bdTeamList.size(), totalSalary, totalRecharge); bdTeamList.size(), totalSalary, totalRecharge);
result.setBdHistoryCO(bdHistoryCO); result.setBdHistoryCO(bdHistoryCO);
@ -180,27 +200,24 @@ public class TeamBdMemberBillListQryExe {
/** /**
* 查询 BD Leader 的团队数据返回 BD 列表每个 BD 汇总其所有团队. * 查询 BD Leader 的团队数据返回 BD 列表每个 BD 汇总其所有团队.
*/ */
private TeamBdMemberCO queryBdLeaderTeamData(Long bdLeaderUserId) { private TeamBdMemberCO queryBdLeaderTeamData(Long bdLeaderUserId, Integer currentBillBelong) {
// 1. 查询该 Leader 下的所有 BD // 1. 查询该 Leader 下的所有 BD
List<BusinessDevelopmentBaseInfo> bdList = List<BusinessDevelopmentBaseInfo> bdList =
businessDevelopmentBaseInfoService.listBdByLeadUserId(bdLeaderUserId); businessDevelopmentBaseInfoService.listBdByLeadUserId(bdLeaderUserId);
if (CollectionUtils.isEmpty(bdList)) { if (CollectionUtils.isEmpty(bdList)) {
return createEmptyResult(); return createEmptyResult(currentBillBelong);
} }
// 2. 获取当前账单周期
Integer currentBillBelong = TeamBillCycleUtils.getCalcBillBelong();
// 3. 收集所有 BD 的用户ID // 3. 收集所有 BD 的用户ID
Set<Long> bdUserIdSet = bdList.stream() Set<Long> bdUserIdSet = bdList.stream()
.map(BusinessDevelopmentBaseInfo::getUserId) .map(BusinessDevelopmentBaseInfo::getUserId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 4. 批量查询所有 BD 的用户信息 // 4. 批量查询所有 BD 的用户信息
Map<Long, UserProfileDTO> bdUserProfileMap = userProfileAppConvertor.toMapUserProfileDTO( Map<Long, UserProfileDTO> bdUserProfileMap = userProfileAppConvertor.toMapUserProfileDTO(
userProfileGateway.mapByUserIds(bdUserIdSet)); userProfileGateway.mapByUserIds(bdUserIdSet));
// 5. 为每个 BD 构建汇总数据 // 5. 为每个 BD 构建汇总数据
List<TeamBdMemberBillCO> memberBillList = bdList.stream() List<TeamBdMemberBillCO> memberBillList = bdList.stream()
.map(bdInfo -> buildBdSummaryBillCO(bdInfo, bdUserProfileMap, currentBillBelong)) .map(bdInfo -> buildBdSummaryBillCO(bdInfo, bdUserProfileMap, currentBillBelong))
@ -213,7 +230,7 @@ public class TeamBdMemberBillListQryExe {
BigDecimal totalRecharge = memberBillList.stream() BigDecimal totalRecharge = memberBillList.stream()
.map(TeamBdMemberBillCO::getTeamRechargeAmount) .map(TeamBdMemberBillCO::getTeamRechargeAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
// 7. 计算总的 Agent 数量所有 BD Agent 总和 // 7. 计算总的 Agent 数量所有 BD Agent 总和
Integer totalAgentCount = memberBillList.stream() Integer totalAgentCount = memberBillList.stream()
.map(TeamBdMemberBillCO::getTeamMemberCount) .map(TeamBdMemberBillCO::getTeamMemberCount)
@ -228,9 +245,9 @@ public class TeamBdMemberBillListQryExe {
result.setTotalSalary(totalSalary); result.setTotalSalary(totalSalary);
result.setTotalRecharge(totalRecharge); result.setTotalRecharge(totalRecharge);
result.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(currentBillBelong)); result.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(currentBillBelong));
// 9. 计算当前账单的 BD Leader 工资信息使用总的 Agent 数量 // 9. 计算当前账单的 BD Leader 工资信息使用总的 Agent 数量
BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong, BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong,
totalAgentCount, totalSalary, totalRecharge); totalAgentCount, totalSalary, totalRecharge);
bdHistoryCO.setBdNumber(bdList.size()); bdHistoryCO.setBdNumber(bdList.size());
result.setBdHistoryCO(bdHistoryCO); result.setBdHistoryCO(bdHistoryCO);
@ -245,9 +262,9 @@ public class TeamBdMemberBillListQryExe {
BusinessDevelopmentBaseInfo bdInfo, BusinessDevelopmentBaseInfo bdInfo,
Map<Long, UserProfileDTO> bdUserProfileMap, Map<Long, UserProfileDTO> bdUserProfileMap,
Integer currentBillBelong) { Integer currentBillBelong) {
TeamBdMemberBillCO co = new TeamBdMemberBillCO(); TeamBdMemberBillCO co = new TeamBdMemberBillCO();
// 1. 设置 BD 用户信息这里的 Agent 字段实际上是 BD 信息 // 1. 设置 BD 用户信息这里的 Agent 字段实际上是 BD 信息
UserProfileDTO bdUserProfile = bdUserProfileMap.get(bdInfo.getUserId()); UserProfileDTO bdUserProfile = bdUserProfileMap.get(bdInfo.getUserId());
if (bdUserProfile != null) { if (bdUserProfile != null) {
@ -256,11 +273,11 @@ public class TeamBdMemberBillListQryExe {
co.setAgentName(bdUserProfile.getUserNickname()); co.setAgentName(bdUserProfile.getUserNickname());
co.setAgentAvatar(bdUserProfile.getUserAvatar()); co.setAgentAvatar(bdUserProfile.getUserAvatar());
} }
// 2. 查询该 BD 的所有团队 // 2. 查询该 BD 的所有团队
List<BusinessDevelopmentTeam> bdTeamList = List<BusinessDevelopmentTeam> bdTeamList =
businessDevelopmentTeamService.listByUserId(bdInfo.getUserId()); businessDevelopmentTeamService.listByUserId(bdInfo.getUserId());
if (CollectionUtils.isEmpty(bdTeamList)) { if (CollectionUtils.isEmpty(bdTeamList)) {
co.setTeamMemberCount(0); co.setTeamMemberCount(0);
co.setTeamSalaryAmount(BigDecimal.ZERO); co.setTeamSalaryAmount(BigDecimal.ZERO);
@ -269,30 +286,28 @@ public class TeamBdMemberBillListQryExe {
co.setEndDate(""); co.setEndDate("");
return co; return co;
} }
// 3. 设置团队数量 BD 管理的 Agent 数量 // 3. 设置团队数量 BD 管理的 Agent 数量
co.setTeamMemberCount(bdTeamList.size()); co.setTeamMemberCount(bdTeamList.size());
// 4. 账单周期日期 // 4. 账单周期日期
String[] dates = TeamBillCycleUtils.parseBillBelongToDateRange(currentBillBelong); String[] dates = TeamBillCycleUtils.parseBillBelongToDateRange(currentBillBelong);
co.setStartDate(dates[0]); co.setStartDate(dates[0]);
co.setEndDate(dates[1]); co.setEndDate(dates[1]);
// 5. 获取团队ID集合 // 5. 获取团队ID集合
Set<Long> teamIdSet = bdTeamList.stream() Set<Long> teamIdSet = bdTeamList.stream()
.map(BusinessDevelopmentTeam::getTeamId) .map(BusinessDevelopmentTeam::getTeamId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 6. 查询账单数据 // 6. 查询账单数据
Map<Long, TeamBillCycle> billCycleMap = teamBillCycleService.mapByTeamIds2( Map<Long, TeamBillCycle> billCycleMap = getBillCycleMap(teamIdSet, currentBillBelong);
teamIdSet, currentBillBelong, TeamBillCycleStatus.UNPAID);
Map<String, TeamPolicyManager> regionPolicyMap = teamPolicyManagerService.mapRegionReleaseWithCountry( Map<String, TeamPolicyManager> regionPolicyMap = teamPolicyManagerService.mapRegionReleaseWithCountry(
billCycleMap.values().stream().map(TeamBillCycle::getRegion).collect(Collectors.toSet())); billCycleMap.values().stream().map(TeamBillCycle::getRegion).collect(Collectors.toSet()));
// 4. 查询当前周期的账单 // 4. 查询当前周期的账单
Map<Long, List<TeamMemberTarget>> memberBillTarget = Map<Long, List<TeamMemberTarget>> memberBillTarget = getMemberBillTargetMap(teamIdSet, currentBillBelong);
teamMemberTargetService.mapMemberBillTarget(teamIdSet, currentBillBelong);
BigDecimal totalSalary = BigDecimal.ZERO; BigDecimal totalSalary = BigDecimal.ZERO;
for (BusinessDevelopmentTeam bdTeam : bdTeamList) { for (BusinessDevelopmentTeam bdTeam : bdTeamList) {
@ -300,27 +315,25 @@ public class TeamBdMemberBillListQryExe {
totalSalary = totalSalary.add(getTeamTotalSalary(billCycleMap, memberBillTarget, regionPolicyMap, teamId)); totalSalary = totalSalary.add(getTeamTotalSalary(billCycleMap, memberBillTarget, regionPolicyMap, teamId));
} }
co.setTeamSalaryAmount(totalSalary); co.setTeamSalaryAmount(totalSalary);
// 8. 获取所有 Agent ID // 8. 获取所有 Agent ID
Set<Long> teamIds = bdTeamList.stream() Set<Long> teamIds = bdTeamList.stream()
.map(BusinessDevelopmentTeam::getTeamId) .map(BusinessDevelopmentTeam::getTeamId)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
// 9. 查询充值数据并汇总 // 9. 查询充值数据并汇总
Map<Long, BigDecimal> rechargeMap = getRechargeMap(currentBillBelong, teamIds); Map<Long, BigDecimal> rechargeMap = getRechargeMap(currentBillBelong, teamIds);
BigDecimal totalRecharge = rechargeMap.values().stream() BigDecimal totalRecharge = rechargeMap.values().stream()
.reduce(BigDecimal.ZERO, BigDecimal::add); .reduce(BigDecimal.ZERO, BigDecimal::add);
co.setTeamRechargeAmount(totalRecharge); co.setTeamRechargeAmount(totalRecharge);
return co; return co;
} }
/** /**
* 创建空结果. * 创建空结果.
*/ */
private TeamBdMemberCO createEmptyResult() { private TeamBdMemberCO createEmptyResult(Integer currentBillBelong) {
Integer currentBillBelong = TeamBillCycleUtils.getCalcBillBelong();
TeamBdMemberCO result = new TeamBdMemberCO(); TeamBdMemberCO result = new TeamBdMemberCO();
result.setMemberBillList(Collections.emptyList()); result.setMemberBillList(Collections.emptyList());
result.setMemberCount(0); result.setMemberCount(0);
@ -329,15 +342,34 @@ public class TeamBdMemberBillListQryExe {
result.setTotalSalary(BigDecimal.ZERO); result.setTotalSalary(BigDecimal.ZERO);
result.setTotalRecharge(BigDecimal.ZERO); result.setTotalRecharge(BigDecimal.ZERO);
result.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(currentBillBelong)); result.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(currentBillBelong));
// 空结果也要计算 BD 工资信息全部为0 // 空结果也要计算 BD 工资信息全部为0
BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong, BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong,
0, BigDecimal.ZERO, BigDecimal.ZERO); 0, BigDecimal.ZERO, BigDecimal.ZERO);
result.setBdHistoryCO(bdHistoryCO); result.setBdHistoryCO(bdHistoryCO);
return result; return result;
} }
private Map<Long, TeamBillCycle> getBillCycleMap(Set<Long> teamIdSet, Integer billBelong) {
Map<Long, TeamBillCycle> billCycleMap = new HashMap<>(
teamBillCycleService.mapByTeamIds2(teamIdSet, billBelong, TeamBillCycleStatus.SETTLED));
billCycleMap.putAll(teamBillCycleService.mapByTeamIds2(teamIdSet, billBelong, TeamBillCycleStatus.UNPAID));
return billCycleMap;
}
private Map<Long, List<TeamMemberTarget>> getMemberBillTargetMap(Set<Long> teamIdSet, Integer billBelong) {
if (CollectionUtils.isEmpty(teamIdSet)) {
return Collections.emptyMap();
}
if (String.valueOf(billBelong).length() == 6) {
Map<Long, List<TeamMemberTarget>> result = new HashMap<>();
teamIdSet.forEach(teamId -> result.put(teamId, teamMemberTargetService.listByMonthlyBillBelong(teamId, billBelong)));
return result;
}
return teamMemberTargetService.mapMemberBillTarget(teamIdSet, billBelong);
}
@NotNull @NotNull
private Map<Long, BigDecimal> getRechargeMap(Integer currentBillBelong, Set<Long> teamIds) { private Map<Long, BigDecimal> getRechargeMap(Integer currentBillBelong, Set<Long> teamIds) {
if (CollectionUtils.isEmpty(teamIds)) { if (CollectionUtils.isEmpty(teamIds)) {
@ -404,7 +436,7 @@ public class TeamBdMemberBillListQryExe {
Integer billBelong) { Integer billBelong) {
TeamBdMemberBillCO co = new TeamBdMemberBillCO(); TeamBdMemberBillCO co = new TeamBdMemberBillCO();
// 代理用户信息 // 代理用户信息
UserProfileDTO userProfile = userProfileMap.get(bdTeam.getAgentId()); UserProfileDTO userProfile = userProfileMap.get(bdTeam.getAgentId());
if (userProfile != null) { if (userProfile != null) {
@ -416,7 +448,7 @@ public class TeamBdMemberBillListQryExe {
// 团队信息 // 团队信息
co.setTeamId(bdTeam.getTeamId()); co.setTeamId(bdTeam.getTeamId());
TeamProfile teamProfile = teamProfileMap.get(bdTeam.getTeamId()); TeamProfile teamProfile = teamProfileMap.get(bdTeam.getTeamId());
if (teamProfile != null) { if (teamProfile != null) {
Long memberCount = Optional.ofNullable(teamProfile.getCounter()) Long memberCount = Optional.ofNullable(teamProfile.getCounter())
@ -449,8 +481,15 @@ public class TeamBdMemberBillListQryExe {
} }
List<TeamMemberTarget> targetList = teamMemberTargetMap.get(teamId); List<TeamMemberTarget> targetList = teamMemberTargetMap.get(teamId);
List<TeamBillMemberTarget> memberTargets = TeamBillCycleUtils.calculatorPolicyTargetWithMap(targetList, policyManagerMap); if (CollectionUtils.isNotEmpty(targetList)) {
return ownTotalSalary(memberTargets); List<TeamBillMemberTarget> memberTargets = TeamBillCycleUtils.calculatorPolicyTargetWithMap(targetList, policyManagerMap);
return ownTotalSalary(memberTargets);
}
return Optional.ofNullable(billCycle.getSettleResult())
.map(TeamBillSettleResult::getTotalSalary)
.orElse(BigDecimal.ZERO)
.setScale(2, RoundingMode.DOWN);
} }
private BigDecimal ownTotalSalary(List<TeamBillMemberTarget> teamBillMemberTargets) { private BigDecimal ownTotalSalary(List<TeamBillMemberTarget> teamBillMemberTargets) {
@ -470,7 +509,7 @@ public class TeamBdMemberBillListQryExe {
*/ */
private BdHistoryCO calculateCurrentBdBill(Integer billBelong, private BdHistoryCO calculateCurrentBdBill(Integer billBelong,
Integer agencyNumber, Integer agencyNumber,
BigDecimal teamSalaryAmount, BigDecimal teamSalaryAmount,
BigDecimal teamRechargeAmount) { BigDecimal teamRechargeAmount) {
String billTitle = TeamBillCycleUtils.parseBillBelongToDateRangeStr(billBelong); String billTitle = TeamBillCycleUtils.parseBillBelongToDateRangeStr(billBelong);
@ -482,22 +521,19 @@ public class TeamBdMemberBillListQryExe {
currentBill.setTeamRechargeAmount(teamRechargeAmount); currentBill.setTeamRechargeAmount(teamRechargeAmount);
currentBill.setStatus(BdSettlementStatus.PENDING.name()); currentBill.setStatus(BdSettlementStatus.PENDING.name());
currentBill.setStatusText("In Progress"); currentBill.setStatusText("In Progress");
// 创建临时结算记录用于政策匹配
BdSalarySettlementRecord tempRecord = new BdSalarySettlementRecord();
// 获取所有启用的政策 // 获取所有启用的政策
List<BdSalaryPolicy> salaryPolicies = bdSalaryPolicyService.listEnabledByType(BdPolicyType.TEAM_SALARY); List<BdSalaryPolicy> salaryPolicies = bdSalaryPolicyService.listEnabledByType(BdPolicyType.TEAM_SALARY);
List<BdSalaryPolicy> rechargePolicies = bdSalaryPolicyService.listEnabledByType(BdPolicyType.TEAM_RECHARGE); List<BdSalaryPolicy> rechargePolicies = bdSalaryPolicyService.listEnabledByType(BdPolicyType.TEAM_RECHARGE);
// 匹配政策1Team Salary Policy // 匹配政策1Team Salary Policy
SalaryCalcResult salaryResult = BdSalarySettlementServiceImpl.matchAndCalculate( SalaryCalcResult salaryResult = BdSalarySettlementServiceImpl.matchAndCalculate(
salaryPolicies, agencyNumber, teamSalaryAmount); salaryPolicies, agencyNumber, teamSalaryAmount);
currentBill.setBdIncomeSalary(salaryResult.getSalary()); currentBill.setBdIncomeSalary(salaryResult.getSalary());
currentBill.setBdRatioSalary(salaryResult.getCommissionRate()); currentBill.setBdRatioSalary(salaryResult.getCommissionRate());
// 匹配政策2Team Recharge Policy // 匹配政策2Team Recharge Policy
SalaryCalcResult rechargeResult = BdSalarySettlementServiceImpl.matchAndCalculate( SalaryCalcResult rechargeResult = BdSalarySettlementServiceImpl.matchAndCalculate(
rechargePolicies, agencyNumber, teamRechargeAmount); rechargePolicies, agencyNumber, teamRechargeAmount);

View File

@ -21,10 +21,14 @@ import com.red.circle.other.domain.gateway.user.UserProfileGateway;
import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway; import com.red.circle.other.domain.gateway.user.ability.UserRegionGateway;
import com.red.circle.other.domain.model.user.UserProfile; import com.red.circle.other.domain.model.user.UserProfile;
import com.red.circle.other.domain.model.user.ability.RegionConfig; import com.red.circle.other.domain.model.user.ability.RegionConfig;
import com.red.circle.other.infra.database.cache.service.other.EnumConfigCacheService; import com.red.circle.other.infra.database.cache.service.other.EnumConfigCacheService;
import com.red.circle.other.infra.database.mongo.entity.live.RoomSetting; import com.red.circle.other.infra.database.rds.entity.props.PropsBackpack;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService; import com.red.circle.other.infra.database.rds.entity.props.PropsNobleVipAbility;
import com.red.circle.other.infra.database.rds.service.props.PropsVipActualEquityService; import com.red.circle.other.infra.database.rds.service.props.PropsBackpackService;
import com.red.circle.other.infra.database.rds.service.props.PropsNobleVipAbilityService;
import com.red.circle.other.infra.database.mongo.entity.live.RoomSetting;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService;
import com.red.circle.other.infra.database.rds.service.props.PropsVipActualEquityService;
import com.red.circle.other.infra.utils.I18nUtils; import com.red.circle.other.infra.utils.I18nUtils;
import com.red.circle.other.inner.asserts.PropsErrorCode; import com.red.circle.other.inner.asserts.PropsErrorCode;
import com.red.circle.other.inner.endpoint.material.props.PropsNobleVipClient; import com.red.circle.other.inner.endpoint.material.props.PropsNobleVipClient;
@ -46,11 +50,14 @@ import com.red.circle.wallet.inner.model.cmd.DiamondReceiptCmd;
import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd; import com.red.circle.wallet.inner.model.cmd.GoldReceiptCmd;
import com.red.circle.wallet.inner.model.dto.WalletReceiptDTO; import com.red.circle.wallet.inner.model.dto.WalletReceiptDTO;
import com.red.circle.wallet.inner.model.dto.WalletReceiptResDTO; import com.red.circle.wallet.inner.model.dto.WalletReceiptResDTO;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.math.RoundingMode; import java.math.RoundingMode;
import java.util.HashMap; import java.sql.Timestamp;
import java.util.Map; import java.util.HashMap;
import java.util.Objects; import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -73,10 +80,12 @@ public class PropsPurchasingCmdExe {
private final EnumConfigCacheService enumConfigCacheService; private final EnumConfigCacheService enumConfigCacheService;
private final RoomProfileManagerService roomProfileManagerService; private final RoomProfileManagerService roomProfileManagerService;
private final PropsVipActualEquityService propsVipActualEquityService; private final PropsVipActualEquityService propsVipActualEquityService;
private final TaskMqMessage taskMqMessage; private final TaskMqMessage taskMqMessage;
private final PropsNobleVipClient propsNobleVipClient; private final PropsNobleVipClient propsNobleVipClient;
private final UserProfileAppConvertor userProfileAppConvertor; private final UserProfileAppConvertor userProfileAppConvertor;
private final UserRegionGateway userRegionGateway; private final UserRegionGateway userRegionGateway;
private final PropsBackpackService propsBackpackService;
private final PropsNobleVipAbilityService propsNobleVipAbilityService;
public BigDecimal execute(PropsPurchasingCmd cmd) { public BigDecimal execute(PropsPurchasingCmd cmd) {
if (cmd.getDays() <= 0) { if (cmd.getDays() <= 0) {
@ -123,10 +132,12 @@ public class PropsPurchasingCmdExe {
if (commodity.getPropsAbility() instanceof NobleVipAbility nobleVipAbility) { if (commodity.getPropsAbility() instanceof NobleVipAbility nobleVipAbility) {
log.warn("查询赠送参数信息:{}"+JSON.toJSON(commodity)); log.warn("查询赠送参数信息:{}"+JSON.toJSON(commodity));
nobleVipAbility = commodity.getNobleVipAbility(); nobleVipAbility = commodity.getNobleVipAbility();
if (nobleVipAbility.cardId() != null) { expireSupersededNobleVipMaterials(cmd.acceptUser(), commodity, nobleVipAbility,
propsStoreGateway.shippingProductProps(new ProductProps() TimestampUtils.now());
if (nobleVipAbility.cardId() != null) {
propsStoreGateway.shippingProductProps(new ProductProps()
.setInitiateUserId(cmd.requiredReqUserId()) .setInitiateUserId(cmd.requiredReqUserId())
.setAcceptUserId(cmd.acceptUser()) .setAcceptUserId(cmd.acceptUser())
.setDays(cmd.getDays()) .setDays(cmd.getDays())
@ -186,11 +197,70 @@ public class PropsPurchasingCmdExe {
propsVipActualEquityService.save(cmd.getAcceptUserId(), propsVipActualEquityService.save(cmd.getAcceptUserId(),
commodity.getPropsResources().getId(), Long.valueOf(cmd.getDays())); commodity.getPropsResources().getId(), Long.valueOf(cmd.getDays()));
} }
} }
} }
private void expireSupersededNobleVipMaterials(Long userId, PropsStoreCommodity commodity,
private void sendGiveAwayNotice(PropsPurchasingCmd cmd, PropsStoreCommodity commodity) { NobleVipAbility targetAbility, Timestamp now) {
Set<Long> targetIds = nobleVipMaterialIds(targetAbility);
if (Objects.nonNull(commodity.getPropsResources())
&& Objects.nonNull(commodity.getPropsResources().getId())) {
targetIds.add(commodity.getPropsResources().getId());
}
Set<Long> allVipIds = new HashSet<>();
propsNobleVipAbilityService.query().list()
.forEach(ability -> allVipIds.addAll(nobleVipMaterialIds(ability)));
allVipIds.removeAll(targetIds);
if (allVipIds.isEmpty()) {
return;
}
propsBackpackService.update()
.set(PropsBackpack::getExpireTime, now)
.set(PropsBackpack::getUseProps, Boolean.FALSE)
.set(PropsBackpack::getAllowGive, Boolean.FALSE)
.set(PropsBackpack::getUpdateTime, now)
.eq(PropsBackpack::getUserId, userId)
.in(PropsBackpack::getPropsId, allVipIds)
.gt(PropsBackpack::getExpireTime, now)
.execute();
}
private Set<Long> nobleVipMaterialIds(NobleVipAbility ability) {
Set<Long> ids = new HashSet<>();
if (Objects.isNull(ability) || Objects.isNull(ability.getNobleVipAbility())) {
return ids;
}
addPositive(ids, ability.id());
addPositive(ids, ability.avatarFrameId());
addPositive(ids, ability.cardId());
addPositive(ids, ability.chatBubbleId());
addPositive(ids, ability.dataCardId());
return ids;
}
private Set<Long> nobleVipMaterialIds(PropsNobleVipAbility ability) {
Set<Long> ids = new HashSet<>();
if (Objects.isNull(ability)) {
return ids;
}
addPositive(ids, ability.getId());
addPositive(ids, ability.getAvatarFrameId());
addPositive(ids, ability.getCarId());
addPositive(ids, ability.getChatBubbleId());
addPositive(ids, ability.getDataCardId());
return ids;
}
private void addPositive(Set<Long> ids, Long id) {
if (Objects.nonNull(id) && id > 0) {
ids.add(id);
}
}
private void sendGiveAwayNotice(PropsPurchasingCmd cmd, PropsStoreCommodity commodity) {
UserProfileDTO userProfileDTO = userProfileAppConvertor.toUserProfileDTO(userProfileGateway.getByUserId(cmd.requiredReqUserId())); UserProfileDTO userProfileDTO = userProfileAppConvertor.toUserProfileDTO(userProfileGateway.getByUserId(cmd.requiredReqUserId()));
RegionConfig region = userRegionGateway.getRegionConfigByUserId(userProfileDTO.getId()); RegionConfig region = userRegionGateway.getRegionConfigByUserId(userProfileDTO.getId());

View File

@ -32,6 +32,7 @@ import org.springframework.stereotype.Component;
public class TeamBillTask { public class TeamBillTask {
private static final boolean SALARY_SETTLEMENT_PAUSED = true; private static final boolean SALARY_SETTLEMENT_PAUSED = true;
private static final boolean BILL_CYCLE_SWITCH_PAUSED = false;
private final TeamSalaryMqMessage otherMqMessage; private final TeamSalaryMqMessage otherMqMessage;
private final TeamProfileService teamProfileService; private final TeamProfileService teamProfileService;
@ -58,23 +59,21 @@ public class TeamBillTask {
private void processTeamBill() { private void processTeamBill() {
if (SALARY_SETTLEMENT_PAUSED) { if (BILL_CYCLE_SWITCH_PAUSED) {
log.warn("process_team_bill skipped: salary settlement is temporarily paused"); log.warn("process_team_bill skipped: bill cycle switch is temporarily paused");
return; return;
} }
log.warn("开始执行账单处理:{},{}", ZonedDateTimeUtils.nowAsiaRiyadhToInt(), LocalDateTime.now()); log.warn("开始执行账期切换:{},{}", ZonedDateTimeUtils.nowAsiaRiyadhToInt(), LocalDateTime.now());
// 出单
teamBillCycleService.billStatusPayOut(); // 只创建新月账期上月账单保留原状态后续由后台人工结算
consumeProcessPayOutBill();
// 创建本月账单发出结算信号 log.warn("结束账期切换");
consumeProcessPayOutBill(); }
log.warn("结束账单处理");
}
public void processTeamBillRetry() { public void processTeamBillRetry() {
if (SALARY_SETTLEMENT_PAUSED) { if (BILL_CYCLE_SWITCH_PAUSED) {
log.warn("process_team_bill_retry skipped: salary settlement is temporarily paused"); log.warn("process_team_bill_retry skipped: bill cycle switch is temporarily paused");
return; return;
} }
@ -138,12 +137,16 @@ public class TeamBillTask {
.setOperationTime(TimestampUtils.now()) .setOperationTime(TimestampUtils.now())
.setOperationBackUser(teamProfile.getCreateUser()); .setOperationBackUser(teamProfile.getCreateUser());
teamBillCycleService.createIfAbsent(cmd); teamBillCycleService.createIfAbsent(cmd);
otherMqMessage.teamBillSettle(new TeamBillSettleEvent() if (SALARY_SETTLEMENT_PAUSED) {
.setTeamId(teamProfile.getId())); log.warn("团队账单结算消息暂停发送: teamId={}", teamProfile.getId());
} else {
success.incrementAndGet(); otherMqMessage.teamBillSettle(new TeamBillSettleEvent()
.setTeamId(teamProfile.getId()));
}
success.incrementAndGet();
} catch (Exception e) { } catch (Exception e) {
fail.incrementAndGet(); fail.incrementAndGet();

View File

@ -1,14 +1,10 @@
package com.red.circle.other.app.dto.cmd; package com.red.circle.other.app.dto.cmd;
import com.red.circle.common.business.dto.PageQueryCmd;
import com.red.circle.common.business.dto.cmd.AppExtCommand; import com.red.circle.common.business.dto.cmd.AppExtCommand;
import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.time.LocalDate;
/** /**
* BD 成员账单查询命令. * BD 成员账单查询命令.
* *
@ -22,4 +18,9 @@ public class TeamBdMemberBillCmd extends AppExtCommand {
@NotBlank @NotBlank
private String type; private String type;
/**
* 账单月份格式 yyyyMM为空时默认当前月
*/
private Integer billBelong;
} }

View File

@ -115,9 +115,9 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
return Maps.newHashMap(); return Maps.newHashMap();
} }
Criteria criteria = new Criteria().orOperator( Criteria criteria = String.valueOf(billBelong).length() == 6
Criteria.where("billBelong").is(billBelong) ? getBillBelongCriteria(billBelong)
); : new Criteria().orOperator(Criteria.where("billBelong").is(billBelong));
List<TeamBillCycle> teamBillCycles = mongoTemplate.find( List<TeamBillCycle> teamBillCycles = mongoTemplate.find(
Query.query(criteria.and("teamId").in(teamIds).and("status").is(status)), Query.query(criteria.and("teamId").in(teamIds).and("status").is(status)),

View File

@ -21,6 +21,7 @@ import com.red.circle.other.infra.database.mongo.service.team.team.TeamMemberTar
import com.red.circle.other.infra.database.mongo.service.team.team.TeamPolicyManagerService; 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.mongo.service.team.team.TeamProfileService;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamSalaryPaymentService; import com.red.circle.other.infra.database.mongo.service.team.team.TeamSalaryPaymentService;
import com.red.circle.other.inner.enums.team.TeamMemberRole;
import com.red.circle.other.inner.enums.team.TeamPolicyTypeEnum; import com.red.circle.other.inner.enums.team.TeamPolicyTypeEnum;
import com.red.circle.other.inner.model.cmd.team.TeamManualSalaryPaymentCmd; import com.red.circle.other.inner.model.cmd.team.TeamManualSalaryPaymentCmd;
import com.red.circle.other.inner.model.cmd.team.TeamManualSalaryPaymentQryCmd; import com.red.circle.other.inner.model.cmd.team.TeamManualSalaryPaymentQryCmd;
@ -85,8 +86,9 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
public PageResult<TeamManualSalaryPaymentDTO> page(TeamManualSalaryPaymentQryCmd cmd) { public PageResult<TeamManualSalaryPaymentDTO> page(TeamManualSalaryPaymentQryCmd cmd) {
validatePage(cmd); validatePage(cmd);
Integer billBelong = resolveBillBelong(cmd.getBillBelong());
List<ManualSalaryRow> rows = buildRows(cmd.getSysOrigin(), cmd.getCountryCode(), List<ManualSalaryRow> rows = buildRows(cmd.getSysOrigin(), cmd.getCountryCode(),
singletonUserId(cmd.getUserId()), TeamBillCycleUtils.getCalcBillBelong()); singletonUserId(cmd.getUserId()), billBelong);
rows = sortRows(rows, cmd.getSortField(), cmd.getSortOrder()); rows = sortRows(rows, cmd.getSortField(), cmd.getSortOrder());
int cursor = 1; int cursor = 1;
@ -114,7 +116,7 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
public List<TeamManualSalaryPaymentResultDTO> pay(TeamManualSalaryPaymentCmd cmd) { public List<TeamManualSalaryPaymentResultDTO> pay(TeamManualSalaryPaymentCmd cmd) {
validatePay(cmd); validatePay(cmd);
Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); Integer billBelong = resolveBillBelong(cmd.getBillBelong());
String lockKey = MANUAL_SALARY_LOCK_PREFIX + cmd.getSysOrigin() + ":" + cmd.getCountryCode() String lockKey = MANUAL_SALARY_LOCK_PREFIX + cmd.getSysOrigin() + ":" + cmd.getCountryCode()
+ ":" + billBelong; + ":" + billBelong;
ResponseAssert.isTrue(CommonErrorCode.OPERATION_CONFLICT, ResponseAssert.isTrue(CommonErrorCode.OPERATION_CONFLICT,
@ -130,10 +132,13 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
continue; continue;
} }
TeamSalaryPayment payment = teamSalaryPaymentService.createIfAbsent(row.getUserId(),
billBelong, row.getSysOrigin());
for (PolicyPayment policyPayment : row.getPayments()) { for (PolicyPayment policyPayment : row.getPayments()) {
Long receiverUserId = getPaymentReceiverUserId(row, policyPayment);
if (Objects.isNull(receiverUserId)) {
continue;
}
TeamSalaryPayment payment = teamSalaryPaymentService.createIfAbsent(receiverUserId,
billBelong, row.getSysOrigin());
if (policyPayment.getSalary().compareTo(BigDecimal.ZERO) <= 0 if (policyPayment.getSalary().compareTo(BigDecimal.ZERO) <= 0
&& Boolean.TRUE.equals(teamSalaryPaymentService.checkRepeatPayment(payment.getId(), && Boolean.TRUE.equals(teamSalaryPaymentService.checkRepeatPayment(payment.getId(),
row.getUserId(), row.getRegion(), policyPayment.getLevel(), row.getUserId(), row.getRegion(), policyPayment.getLevel(),
@ -141,10 +146,6 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
continue; continue;
} }
Long receiverUserId = policyPayment.isAnchor() ? row.getUserId() : row.getTeamOwnId();
if (Objects.isNull(receiverUserId)) {
continue;
}
BigDecimal issueSalary = policyPayment.getSalary().compareTo(BigDecimal.ZERO) > 0 BigDecimal issueSalary = policyPayment.getSalary().compareTo(BigDecimal.ZERO) > 0
? policyPayment.getSalary() ? policyPayment.getSalary()
: BigDecimal.ZERO.setScale(2, RoundingMode.DOWN); : BigDecimal.ZERO.setScale(2, RoundingMode.DOWN);
@ -240,8 +241,14 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
Map<Long, TeamMember> memberMap = teamMemberService.mapByMemberIds(memberIds); Map<Long, TeamMember> memberMap = teamMemberService.mapByMemberIds(memberIds);
Map<Long, TeamProfile> teamProfileMap = teamProfileService.mapProfileByIds(teamIds); Map<Long, TeamProfile> teamProfileMap = teamProfileService.mapProfileByIds(teamIds);
Set<Integer> salaryPaymentBillBelongs = getMergedSalaryPaymentBillBelongs(billBelong); Set<Integer> salaryPaymentBillBelongs = getMergedSalaryPaymentBillBelongs(billBelong);
Set<Long> paymentRelatedUserIds = new HashSet<>(memberIds);
teamProfileMap.values().stream()
.filter(Objects::nonNull)
.map(TeamProfile::getOwnUserId)
.filter(Objects::nonNull)
.forEach(paymentRelatedUserIds::add);
List<TeamSalaryPayment> salaryPayments = teamSalaryPaymentService List<TeamSalaryPayment> salaryPayments = teamSalaryPaymentService
.listByPaymentOrRecipientUserIdsAndDateNumbers(sysOrigin, memberIds, .listByPaymentOrRecipientUserIdsAndDateNumbers(sysOrigin, paymentRelatedUserIds,
salaryPaymentBillBelongs); salaryPaymentBillBelongs);
Map<String, TeamSalaryPayment> salaryPaymentMap = toSalaryPaymentMap(salaryPayments); Map<String, TeamSalaryPayment> salaryPaymentMap = toSalaryPaymentMap(salaryPayments);
@ -337,9 +344,13 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
if (Objects.isNull(userId) || CollectionUtils.isEmpty(salaryPaymentMap)) { if (Objects.isNull(userId) || CollectionUtils.isEmpty(salaryPaymentMap)) {
return List.of(); return List.of();
} }
return getMergedSalaryPaymentBillBelongs(billBelong).stream() Set<Integer> mergedBillBelongs = getMergedSalaryPaymentBillBelongs(billBelong);
.map(item -> salaryPaymentMap.get(paymentId(userId, item))) return salaryPaymentMap.values().stream()
.filter(Objects::nonNull) .filter(Objects::nonNull)
.filter(payment -> mergedBillBelongs.contains(payment.getDateNumber()))
.filter(payment -> Objects.equals(payment.getUserId(), userId)
|| listValidSalaryDetails(List.of(payment)).stream()
.anyMatch(details -> Objects.equals(details.getTeamMemberId(), userId)))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
@ -368,24 +379,27 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
List<TeamSalaryPayment> cyclePayments = listCyclePayments(salaryPaymentMap, List<TeamSalaryPayment> cyclePayments = listCyclePayments(salaryPaymentMap,
target.getUserId(), billBelong); target.getUserId(), billBelong);
PaymentSummary receivedPaymentSummary = summarizeReceivedPayments(
new ArrayList<>(salaryPaymentMap.values()), target.getUserId());
BigDecimal expectedMemberSalary = salaryValue(billTarget.getMemberSalary()); BigDecimal expectedMemberSalary = salaryValue(billTarget.getMemberSalary());
BigDecimal expectedAgentSalary = salaryValue(billTarget.getOwnSalary()); BigDecimal expectedAgentSalary = Objects.nonNull(teamProfile.getOwnUserId())
BigDecimal expectedSalary = expectedTotalSalary(billTarget); ? salaryValue(billTarget.getOwnSalary())
BigDecimal payableSalary = expectedSalary.subtract(receivedPaymentSummary.getTotalIssuedSalary()) : BigDecimal.ZERO.setScale(2, RoundingMode.DOWN);
BigDecimal expectedSalary = expectedMemberSalary.add(expectedAgentSalary)
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
PaymentSummary receivedPaymentSummary = summarizeTargetEntitlementPayments(cyclePayments,
target.getUserId(), expectedMemberSalary, expectedAgentSalary);
BigDecimal payableMemberSalary = expectedMemberSalary BigDecimal payableMemberSalary = expectedMemberSalary
.subtract(receivedPaymentSummary.getMemberIssuedSalary()) .subtract(receivedPaymentSummary.getMemberIssuedSalary())
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
BigDecimal payableAgentSalary = expectedAgentSalary BigDecimal payableAgentSalary = expectedAgentSalary
.subtract(receivedPaymentSummary.getAgentIssuedSalary()) .subtract(receivedPaymentSummary.getAgentIssuedSalary())
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
BigDecimal payableSalary = payableAmount(payableMemberSalary).add(
payableAmount(payableAgentSalary))
.setScale(2, RoundingMode.DOWN);
boolean currentLevelSettled = areCurrentRolesSettled(cyclePayments, target.getUserId(), region, boolean currentLevelSettled = areCurrentRolesSettled(cyclePayments, target.getUserId(), region,
billTarget, payableSalary); billTarget, receivedPaymentSummary, expectedMemberSalary, expectedAgentSalary);
List<PolicyPayment> policyPayments = buildSettlementPayments(policyManager, billTarget, List<PolicyPayment> policyPayments = buildSettlementPayments(policyManager, billTarget,
cyclePayments, target.getUserId(), region, receivedPaymentSummary, payableSalary, cyclePayments, target.getUserId(), region, receivedPaymentSummary, currentLevelSettled);
currentLevelSettled);
TeamManualSalaryPaymentDTO dto = new TeamManualSalaryPaymentDTO() TeamManualSalaryPaymentDTO dto = new TeamManualSalaryPaymentDTO()
.setId(target.getId()) .setId(target.getId())
@ -396,7 +410,7 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
.setTeamOwnId(teamProfile.getOwnUserId()) .setTeamOwnId(teamProfile.getOwnUserId())
.setUserId(target.getUserId()) .setUserId(target.getUserId())
.setBillBelong(billBelong) .setBillBelong(billBelong)
.setLastSettlementTarget(getLastSettlementTarget(cyclePayments)) .setLastSettlementTarget(getLastSettlementTarget(cyclePayments, target.getUserId()))
.setCurrentTarget(target.sumAcceptGiftValue()) .setCurrentTarget(target.sumAcceptGiftValue())
.setPolicyLevel(billTarget.getLevel()) .setPolicyLevel(billTarget.getLevel())
.setSatisfyLevel(billTarget.getSatisfyLevel()) .setSatisfyLevel(billTarget.getSatisfyLevel())
@ -413,6 +427,7 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
.setMemberOverIssuedSalary(receivedPaymentSummary.getMemberOverIssuedSalary()) .setMemberOverIssuedSalary(receivedPaymentSummary.getMemberOverIssuedSalary())
.setAgentOverIssuedSalary(receivedPaymentSummary.getAgentOverIssuedSalary()) .setAgentOverIssuedSalary(receivedPaymentSummary.getAgentOverIssuedSalary())
.setCurrentSalaryBalance(getAccurateBalance(target.getUserId())) .setCurrentSalaryBalance(getAccurateBalance(target.getUserId()))
.setAgentMember(isAgentMember(member, teamProfile, target))
.setPaid(currentLevelSettled); .setPaid(currentLevelSettled);
return new ManualSalaryRow(dto, target, teamProfile, policyManager, billTarget, policyPayments); return new ManualSalaryRow(dto, target, teamProfile, policyManager, billTarget, policyPayments);
@ -468,34 +483,27 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
.setSatisfyLevel(Boolean.FALSE); .setSatisfyLevel(Boolean.FALSE);
} }
private BigDecimal expectedTotalSalary(TeamBillMemberTarget billTarget) {
if (Objects.isNull(billTarget)) {
return BigDecimal.ZERO.setScale(2, RoundingMode.DOWN);
}
return salaryValue(billTarget.getMemberSalary())
.add(salaryValue(billTarget.getOwnSalary()))
.setScale(2, RoundingMode.DOWN);
}
private BigDecimal salaryValue(BigDecimal value) { private BigDecimal salaryValue(BigDecimal value) {
return Optional.ofNullable(value) return Optional.ofNullable(value)
.orElse(BigDecimal.ZERO) .orElse(BigDecimal.ZERO)
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
} }
private BigDecimal payableAmount(BigDecimal value) {
BigDecimal amount = salaryValue(value);
return amount.compareTo(BigDecimal.ZERO) > 0 ? amount : BigDecimal.ZERO.setScale(2,
RoundingMode.DOWN);
}
private List<PolicyPayment> buildSettlementPayments(TeamPolicyManager policyManager, private List<PolicyPayment> buildSettlementPayments(TeamPolicyManager policyManager,
TeamBillMemberTarget billTarget, List<TeamSalaryPayment> cyclePayments, Long userId, TeamBillMemberTarget billTarget, List<TeamSalaryPayment> cyclePayments, Long userId,
String region, PaymentSummary paymentSummary, BigDecimal payableSalary, String region, PaymentSummary paymentSummary, boolean currentLevelSettled) {
boolean currentLevelSettled) {
if (currentLevelSettled || Objects.isNull(billTarget) if (currentLevelSettled || Objects.isNull(billTarget)
|| Objects.isNull(billTarget.getLevel())) { || Objects.isNull(billTarget.getLevel())) {
return List.of(); return List.of();
} }
TeamPolicy policy = findPolicy(policyManager, billTarget.getLevel()); TeamPolicy policy = findPolicy(policyManager, billTarget.getLevel());
BigDecimal remainingPayable = payableSalary.compareTo(BigDecimal.ZERO) > 0
? payableSalary.setScale(2, RoundingMode.DOWN)
: BigDecimal.ZERO.setScale(2, RoundingMode.DOWN);
BigDecimal memberExpected = salaryValue(billTarget.getMemberSalary()); BigDecimal memberExpected = salaryValue(billTarget.getMemberSalary());
BigDecimal agentExpected = salaryValue(billTarget.getOwnSalary()); BigDecimal agentExpected = salaryValue(billTarget.getOwnSalary());
BigDecimal memberMissing = memberExpected.subtract(paymentSummary.getMemberIssuedSalary()) BigDecimal memberMissing = memberExpected.subtract(paymentSummary.getMemberIssuedSalary())
@ -508,31 +516,17 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
billTarget.getLevel(), Boolean.FALSE); billTarget.getLevel(), Boolean.FALSE);
List<PolicyPayment> payments = new ArrayList<>(); List<PolicyPayment> payments = new ArrayList<>();
if (remainingPayable.compareTo(BigDecimal.ZERO) <= 0) {
if (!memberHasSettlementRecord && memberExpected.compareTo(BigDecimal.ZERO) > 0) {
payments.add(new PolicyPayment(policy, billTarget.getLevel(),
BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.TRUE));
}
if (!agentHasSettlementRecord && agentExpected.compareTo(BigDecimal.ZERO) > 0) {
payments.add(new PolicyPayment(policy, billTarget.getLevel(),
BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.FALSE));
}
return payments;
}
if (memberMissing.compareTo(BigDecimal.ZERO) > 0) { if (memberMissing.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal issueSalary = memberMissing.min(remainingPayable).setScale(2, RoundingMode.DOWN); payments.add(new PolicyPayment(policy, billTarget.getLevel(), memberMissing, Boolean.TRUE));
if (issueSalary.compareTo(BigDecimal.ZERO) > 0) { } else if (!memberHasSettlementRecord && memberExpected.compareTo(BigDecimal.ZERO) > 0) {
payments.add(new PolicyPayment(policy, billTarget.getLevel(), issueSalary, Boolean.TRUE)); payments.add(new PolicyPayment(policy, billTarget.getLevel(),
remainingPayable = remainingPayable.subtract(issueSalary).setScale(2, RoundingMode.DOWN); BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.TRUE));
}
} }
if (agentMissing.compareTo(BigDecimal.ZERO) > 0 if (agentMissing.compareTo(BigDecimal.ZERO) > 0) {
&& remainingPayable.compareTo(BigDecimal.ZERO) > 0) { payments.add(new PolicyPayment(policy, billTarget.getLevel(), agentMissing, Boolean.FALSE));
BigDecimal issueSalary = agentMissing.min(remainingPayable).setScale(2, RoundingMode.DOWN); } else if (!agentHasSettlementRecord && agentExpected.compareTo(BigDecimal.ZERO) > 0) {
if (issueSalary.compareTo(BigDecimal.ZERO) > 0) { payments.add(new PolicyPayment(policy, billTarget.getLevel(),
payments.add(new PolicyPayment(policy, billTarget.getLevel(), issueSalary, Boolean.FALSE)); BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.FALSE));
}
} }
return payments; return payments;
} }
@ -549,21 +543,25 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
} }
private boolean areCurrentRolesSettled(List<TeamSalaryPayment> cyclePayments, Long userId, private boolean areCurrentRolesSettled(List<TeamSalaryPayment> cyclePayments, Long userId,
String region, TeamBillMemberTarget billTarget, BigDecimal payableSalary) { String region, TeamBillMemberTarget billTarget, PaymentSummary paymentSummary,
BigDecimal expectedMemberSalary, BigDecimal expectedAgentSalary) {
if (Objects.isNull(billTarget) || Objects.isNull(billTarget.getLevel()) if (Objects.isNull(billTarget) || Objects.isNull(billTarget.getLevel())
|| payableSalary.compareTo(BigDecimal.ZERO) > 0) { || Objects.isNull(paymentSummary)) {
return false; return false;
} }
return isRoleSettled(cyclePayments, userId, region, billTarget.getLevel(), Boolean.TRUE, return isRoleSettled(cyclePayments, userId, region, billTarget.getLevel(), Boolean.TRUE,
salaryValue(billTarget.getMemberSalary())) expectedMemberSalary, paymentSummary.getMemberIssuedSalary())
&& isRoleSettled(cyclePayments, userId, region, billTarget.getLevel(), Boolean.FALSE, && isRoleSettled(cyclePayments, userId, region, billTarget.getLevel(), Boolean.FALSE,
salaryValue(billTarget.getOwnSalary())); expectedAgentSalary, paymentSummary.getAgentIssuedSalary());
} }
private boolean isRoleSettled(List<TeamSalaryPayment> cyclePayments, Long userId, String region, private boolean isRoleSettled(List<TeamSalaryPayment> cyclePayments, Long userId, String region,
Integer policyLevel, Boolean anchor, BigDecimal expectedSalary) { Integer policyLevel, Boolean anchor, BigDecimal expectedSalary, BigDecimal issuedSalary) {
return expectedSalary.compareTo(BigDecimal.ZERO) <= 0 if (expectedSalary.compareTo(BigDecimal.ZERO) <= 0) {
|| hasPaidPolicy(cyclePayments, userId, region, policyLevel, anchor); return true;
}
return Optional.ofNullable(issuedSalary).orElse(BigDecimal.ZERO).compareTo(expectedSalary) >= 0
&& hasPaidPolicy(cyclePayments, userId, region, policyLevel, anchor);
} }
private boolean hasPaidPolicy(List<TeamSalaryPayment> cyclePayments, Long userId, String region, private boolean hasPaidPolicy(List<TeamSalaryPayment> cyclePayments, Long userId, String region,
@ -575,36 +573,39 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
&& Objects.equals(details.getAnchor(), anchor)); && Objects.equals(details.getAnchor(), anchor));
} }
private Long getLastSettlementTarget(List<TeamSalaryPayment> cyclePayments) { private Long getLastSettlementTarget(List<TeamSalaryPayment> cyclePayments, Long targetUserId) {
return listValidSalaryDetails(cyclePayments).stream() return listValidSalaryDetails(cyclePayments).stream()
.filter(details -> Objects.equals(details.getTeamMemberId(), targetUserId))
.max(Comparator.comparing(details -> Optional.ofNullable(details.getCreateTime()) .max(Comparator.comparing(details -> Optional.ofNullable(details.getCreateTime())
.orElse(new Timestamp(0)))) .orElse(new Timestamp(0))))
.map(TeamSalaryPaymentDetails::getTarget) .map(TeamSalaryPaymentDetails::getTarget)
.orElse(0L); .orElse(0L);
} }
private PaymentSummary summarizePayments(List<TeamSalaryPayment> cyclePayments) { private PaymentSummary summarizeTargetEntitlementPayments(List<TeamSalaryPayment> cyclePayments,
return summarizeDetails(listValidSalaryDetails(cyclePayments)); Long targetUserId, BigDecimal expectedMemberSalary, BigDecimal expectedAgentSalary) {
} if (Objects.isNull(targetUserId)) {
private PaymentSummary summarizeReceivedPayments(List<TeamSalaryPayment> cyclePayments,
Long receiverUserId) {
if (Objects.isNull(receiverUserId)) {
return emptyPaymentSummary(); return emptyPaymentSummary();
} }
List<TeamSalaryPaymentDetails> details = listValidSalaryDetails(cyclePayments).stream() List<TeamSalaryPaymentDetails> details = listValidSalaryDetails(cyclePayments).stream()
.filter(detail -> Objects.equals(receiverUserId, getSalaryReceiverUserId(detail))) .filter(detail -> Objects.equals(targetUserId, detail.getTeamMemberId()))
.collect(Collectors.toList()); .collect(Collectors.toList());
return summarizeDetails(details); return summarizeDetails(details, expectedMemberSalary, expectedAgentSalary);
} }
private PaymentSummary summarizeDetails(List<TeamSalaryPaymentDetails> details) { private PaymentSummary summarizeDetails(List<TeamSalaryPaymentDetails> details,
BigDecimal expectedMemberSalary, BigDecimal expectedAgentSalary) {
BigDecimal memberIssuedSalary = sumDetailsSalary(details, Boolean.TRUE); BigDecimal memberIssuedSalary = sumDetailsSalary(details, Boolean.TRUE);
BigDecimal agentIssuedSalary = sumDetailsSalary(details, Boolean.FALSE); BigDecimal agentIssuedSalary = sumDetailsSalary(details, Boolean.FALSE);
BigDecimal totalIssuedSalary = memberIssuedSalary.add(agentIssuedSalary) BigDecimal totalIssuedSalary = memberIssuedSalary.add(agentIssuedSalary)
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
BigDecimal memberOverIssuedSalary = getOverIssuedSalary(details, Boolean.TRUE); BigDecimal memberOverIssuedSalary = memberIssuedSalary.subtract(
BigDecimal agentOverIssuedSalary = getOverIssuedSalary(details, Boolean.FALSE); salaryValue(expectedMemberSalary))
.max(BigDecimal.ZERO)
.setScale(2, RoundingMode.DOWN);
BigDecimal agentOverIssuedSalary = agentIssuedSalary.subtract(salaryValue(expectedAgentSalary))
.max(BigDecimal.ZERO)
.setScale(2, RoundingMode.DOWN);
BigDecimal overIssuedSalary = memberOverIssuedSalary.add(agentOverIssuedSalary) BigDecimal overIssuedSalary = memberOverIssuedSalary.add(agentOverIssuedSalary)
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
return new PaymentSummary(totalIssuedSalary, memberIssuedSalary, agentIssuedSalary, return new PaymentSummary(totalIssuedSalary, memberIssuedSalary, agentIssuedSalary,
@ -616,17 +617,6 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
return new PaymentSummary(zero, zero, zero, zero, zero, zero); return new PaymentSummary(zero, zero, zero, zero, zero, zero);
} }
private Long getSalaryReceiverUserId(TeamSalaryPaymentDetails details) {
if (Objects.isNull(details)) {
return null;
}
if (Boolean.TRUE.equals(details.getAnchor())
&& !Boolean.TRUE.equals(details.getHostSalaryToAgent())) {
return details.getTeamMemberId();
}
return details.getTeamOwnId();
}
private BigDecimal sumDetailsSalary(List<TeamSalaryPaymentDetails> details, Boolean anchor) { private BigDecimal sumDetailsSalary(List<TeamSalaryPaymentDetails> details, Boolean anchor) {
return details.stream() return details.stream()
.filter(detail -> Objects.equals(detail.getAnchor(), anchor)) .filter(detail -> Objects.equals(detail.getAnchor(), anchor))
@ -635,38 +625,6 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
} }
private BigDecimal getOverIssuedSalary(List<TeamSalaryPaymentDetails> details) {
return getOverIssuedSalary(details, null);
}
private BigDecimal getOverIssuedSalary(List<TeamSalaryPaymentDetails> details, Boolean anchor) {
if (CollectionUtils.isEmpty(details)) {
return BigDecimal.ZERO.setScale(2, RoundingMode.DOWN);
}
Set<String> issuedKeys = new HashSet<>();
return details.stream()
.filter(detail -> Objects.isNull(anchor) || Objects.equals(detail.getAnchor(), anchor))
.sorted(Comparator.comparing(detail -> Optional.ofNullable(detail.getCreateTime())
.orElse(new Timestamp(0))))
.filter(detail -> !issuedKeys.add(paymentDetailsPolicyKey(detail)))
.map(detail -> Optional.ofNullable(detail.getSalary()).orElse(BigDecimal.ZERO))
.reduce(BigDecimal.ZERO, BigDecimal::add)
.setScale(2, RoundingMode.DOWN);
}
private String paymentDetailsPolicyKey(TeamSalaryPaymentDetails details) {
if (Objects.isNull(details)
|| Objects.isNull(details.getTeamMemberId())
|| StringUtils.isBlank(details.getTeamRegionId())
|| Objects.isNull(details.getPolicyLevel())
|| Objects.isNull(details.getAnchor())) {
return Optional.ofNullable(details).map(TeamSalaryPaymentDetails::getId)
.orElse(String.valueOf(System.identityHashCode(details)));
}
return details.getTeamMemberId() + ":" + details.getTeamRegionId() + ":"
+ details.getPolicyLevel() + ":" + details.getAnchor();
}
private List<TeamSalaryPaymentDetails> listValidSalaryDetails( private List<TeamSalaryPaymentDetails> listValidSalaryDetails(
List<TeamSalaryPayment> cyclePayments) { List<TeamSalaryPayment> cyclePayments) {
if (CollectionUtils.isEmpty(cyclePayments)) { if (CollectionUtils.isEmpty(cyclePayments)) {
@ -729,6 +687,25 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
teamSalaryPaymentService.updatePolicyMap(paymentId, policyMap); teamSalaryPaymentService.updatePolicyMap(paymentId, policyMap);
} }
private Integer resolveBillBelong(Integer billBelong) {
return Objects.nonNull(billBelong) ? billBelong : TeamBillCycleUtils.getCalcBillBelong();
}
private Long getPaymentReceiverUserId(ManualSalaryRow row, PolicyPayment payment) {
if (Objects.isNull(row) || Objects.isNull(payment)) {
return null;
}
return payment.isAnchor() ? row.getUserId() : row.getTeamOwnId();
}
private boolean isAgentMember(TeamMember member, TeamProfile teamProfile,
TeamMemberTarget target) {
return Objects.nonNull(target)
&& ((Objects.nonNull(teamProfile)
&& Objects.equals(teamProfile.getOwnUserId(), target.getUserId()))
|| (Objects.nonNull(member) && TeamMemberRole.OWN.eq(member.getRole())));
}
private void validatePage(TeamManualSalaryPaymentQryCmd cmd) { private void validatePage(TeamManualSalaryPaymentQryCmd cmd) {
ResponseAssert.notNull(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd); ResponseAssert.notNull(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd);
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getSysOrigin()); ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getSysOrigin());
@ -741,10 +718,6 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getCountryCode()); ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getCountryCode());
} }
private String paymentId(Long userId, Integer billBelong) {
return userId + "_" + billBelong;
}
private static class PaymentSummary { private static class PaymentSummary {
private final BigDecimal totalIssuedSalary; private final BigDecimal totalIssuedSalary;

View File

@ -27,6 +27,7 @@ import com.red.circle.other.infra.database.mongo.service.team.team.TeamPolicyMan
import com.red.circle.other.infra.database.mongo.service.team.team.TeamProfileService; import com.red.circle.other.infra.database.mongo.service.team.team.TeamProfileService;
import com.red.circle.other.infra.database.mongo.service.team.team.TeamSalaryPaymentService; import com.red.circle.other.infra.database.mongo.service.team.team.TeamSalaryPaymentService;
import com.red.circle.other.infra.database.mongo.entity.team.team.TeamSalaryPayment; import com.red.circle.other.infra.database.mongo.entity.team.team.TeamSalaryPayment;
import com.red.circle.other.inner.enums.team.TeamMemberRole;
import com.red.circle.other.inner.enums.team.TeamMemberTargetIndex; import com.red.circle.other.inner.enums.team.TeamMemberTargetIndex;
import com.red.circle.other.inner.model.cmd.team.TeamManualSalaryPaymentCmd; import com.red.circle.other.inner.model.cmd.team.TeamManualSalaryPaymentCmd;
import com.red.circle.other.inner.model.dto.agency.agency.TeamManualSalaryPaymentResultDTO; import com.red.circle.other.inner.model.dto.agency.agency.TeamManualSalaryPaymentResultDTO;
@ -38,6 +39,7 @@ import java.math.BigDecimal;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.test.util.ReflectionTestUtils;
@ -73,13 +75,16 @@ class TeamManualSalaryPaymentClientServiceImplTest {
List<?> payments = ReflectionTestUtils.invokeMethod(service, "buildSettlementPayments", List<?> payments = ReflectionTestUtils.invokeMethod(service, "buildSettlementPayments",
null, billTarget, List.of(currentPayment), userId, region, paymentSummary, null, billTarget, List.of(currentPayment), userId, region, paymentSummary,
new BigDecimal("12.25"), false); false);
assertEquals(1, payments.size()); assertEquals(2, payments.size());
Object payment = payments.get(0); Object payment = payments.get(0);
assertEquals(6, ReflectionTestUtils.getField(payment, "level")); assertEquals(6, ReflectionTestUtils.getField(payment, "level"));
assertEquals(Boolean.TRUE, ReflectionTestUtils.getField(payment, "anchor")); assertEquals(Boolean.TRUE, ReflectionTestUtils.getField(payment, "anchor"));
assertEquals(new BigDecimal("12.25"), ReflectionTestUtils.getField(payment, "salary")); assertEquals(new BigDecimal("13.00"), ReflectionTestUtils.getField(payment, "salary"));
assertEquals(Boolean.FALSE, ReflectionTestUtils.getField(payments.get(1), "anchor"));
assertEquals(BigDecimal.ZERO.setScale(2), ReflectionTestUtils.getField(payments.get(1),
"salary"));
} }
@Test @Test
@ -101,7 +106,7 @@ class TeamManualSalaryPaymentClientServiceImplTest {
List<?> payments = ReflectionTestUtils.invokeMethod(service, "buildSettlementPayments", List<?> payments = ReflectionTestUtils.invokeMethod(service, "buildSettlementPayments",
null, billTarget, List.of(), userId, region, paymentSummary, null, billTarget, List.of(), userId, region, paymentSummary,
new BigDecimal("-1.25"), false); false);
assertEquals(2, payments.size()); assertEquals(2, payments.size());
assertEquals(BigDecimal.ZERO.setScale(2), ReflectionTestUtils.getField(payments.get(0), assertEquals(BigDecimal.ZERO.setScale(2), ReflectionTestUtils.getField(payments.get(0),
@ -193,7 +198,8 @@ class TeamManualSalaryPaymentClientServiceImplTest {
.thenReturn(new TeamSalaryPayment().setId(paymentId)); .thenReturn(new TeamSalaryPayment().setId(paymentId));
when(userBankBalanceClient.getBalance(userId)) when(userBankBalanceClient.getBalance(userId))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(0L))) .thenReturn(ResultResponse.success(BankBalanceDTO.of(0L)))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(1225L))); .thenReturn(ResultResponse.success(BankBalanceDTO.of(0L)))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(1300L)));
when(userBankBalanceClient.incr(any())).thenReturn(ResultResponse.success(Boolean.TRUE)); when(userBankBalanceClient.incr(any())).thenReturn(ResultResponse.success(Boolean.TRUE));
when(userBankRunningWaterClient.add(any())).thenReturn(ResultResponse.success()); when(userBankRunningWaterClient.add(any())).thenReturn(ResultResponse.success());
when(teamSalaryPaymentService.mapPolicy(paymentId)).thenReturn(new HashMap<>()); when(teamSalaryPaymentService.mapPolicy(paymentId)).thenReturn(new HashMap<>());
@ -205,16 +211,212 @@ class TeamManualSalaryPaymentClientServiceImplTest {
assertEquals(1, results.size()); assertEquals(1, results.size());
assertEquals(userId, results.get(0).getUserId()); assertEquals(userId, results.get(0).getUserId());
assertEquals(new BigDecimal("12.25"), results.get(0).getIssuedSalary()); assertEquals(new BigDecimal("13.00"), results.get(0).getIssuedSalary());
ArgumentCaptor<TeamSalaryPaymentDetails> detailsCaptor = ArgumentCaptor<TeamSalaryPaymentDetails> detailsCaptor =
ArgumentCaptor.forClass(TeamSalaryPaymentDetails.class); ArgumentCaptor.forClass(TeamSalaryPaymentDetails.class);
verify(teamSalaryPaymentService).paymentSalary(eq(paymentId), detailsCaptor.capture()); verify(teamSalaryPaymentService).paymentSalary(eq(paymentId), detailsCaptor.capture());
assertEquals(new BigDecimal("12.25"), detailsCaptor.getValue().getSalary()); assertEquals(new BigDecimal("13.00"), detailsCaptor.getValue().getSalary());
assertEquals(Boolean.TRUE, detailsCaptor.getValue().getAnchor()); assertEquals(Boolean.TRUE, detailsCaptor.getValue().getAnchor());
verify(teamSalaryPaymentService, never()).checkRepeatPayment(anyString(), anyLong(), verify(teamSalaryPaymentService, never()).checkRepeatPayment(anyString(), anyLong(),
anyString(), any(), any()); anyString(), any(), any());
} }
@Test
void payShouldIssueMemberSalaryToAnchorAndAgentSalaryToTeamOwner() {
RedisService redisService = mock(RedisService.class);
TeamMemberService teamMemberService = mock(TeamMemberService.class);
TeamProfileService teamProfileService = mock(TeamProfileService.class);
UserBankBalanceClient userBankBalanceClient = mock(UserBankBalanceClient.class);
TeamMemberTargetService teamMemberTargetService = mock(TeamMemberTargetService.class);
TeamSalaryPaymentService teamSalaryPaymentService = mock(TeamSalaryPaymentService.class);
TeamPolicyManagerService teamPolicyManagerService = mock(TeamPolicyManagerService.class);
UserBankRunningWaterClient userBankRunningWaterClient = mock(UserBankRunningWaterClient.class);
TeamManualSalaryPaymentClientServiceImpl service = new TeamManualSalaryPaymentClientServiceImpl(
redisService, teamMemberService, teamProfileService, userBankBalanceClient,
teamMemberTargetService, teamSalaryPaymentService, teamPolicyManagerService,
userBankRunningWaterClient);
String sysOrigin = "LIKEI";
String countryCode = "PH";
String region = "2046067717605224449";
Long anchorUserId = 2054591808909930498L;
Long ownerUserId = 2057118411766427649L;
Long teamId = 2054614353419759617L;
Integer billBelong = TeamBillCycleUtils.getCalcBillBelong();
String anchorPaymentId = anchorUserId + "_" + billBelong;
String ownerPaymentId = ownerUserId + "_" + billBelong;
TeamMemberTarget target = new TeamMemberTarget()
.setId("target-id")
.setTimeId(2056024761217118210L)
.setSysOrigin(sysOrigin)
.setRegion(region)
.setCountryCode(countryCode)
.setTeamId(teamId)
.setUserId(anchorUserId)
.setBillBelong(billBelong)
.setDailyTargets(List.of(new TeamMemberTargetIndex()
.setOwnOnlineTime(717L)
.setAcceptGiftValue(15151819L)));
TeamPolicyManager policyManager = new TeamPolicyManager()
.setSysOrigin(sysOrigin)
.setRegion(region)
.setCountryCode(countryCode)
.setPolicy(List.of(new TeamPolicy()
.setLevel(6)
.setTarget(8810000L)
.setOnlineTime(10L)
.setEffectiveDay(0)
.setMemberSalary(new BigDecimal("15.00"))
.setOwnSalary(new BigDecimal("3.75"))
.setTotalSalary(new BigDecimal("18.75"))));
when(redisService.lock(anyString(), anyLong())).thenReturn(true);
when(teamMemberTargetService.listManualSalaryTargets(eq(sysOrigin), eq(countryCode), anySet(),
eq(billBelong))).thenReturn(List.of(target));
when(teamMemberService.mapByMemberIds(anySet())).thenReturn(Map.of(anchorUserId,
new TeamMember().setMemberId(anchorUserId).setTeamId(teamId)
.setRole(TeamMemberRole.MEMBER)));
when(teamProfileService.mapProfileByIds(anySet())).thenReturn(Map.of(teamId,
new TeamProfile().setId(teamId).setOwnUserId(ownerUserId).setRegion(region)));
when(teamSalaryPaymentService.listByPaymentOrRecipientUserIdsAndDateNumbers(eq(sysOrigin),
anySet(), anySet())).thenReturn(List.of());
when(teamPolicyManagerService.getReleaseByRegionAndType(sysOrigin, region, null, countryCode))
.thenReturn(policyManager);
when(teamSalaryPaymentService.createIfAbsent(anchorUserId, billBelong, sysOrigin))
.thenReturn(new TeamSalaryPayment().setId(anchorPaymentId));
when(teamSalaryPaymentService.createIfAbsent(ownerUserId, billBelong, sysOrigin))
.thenReturn(new TeamSalaryPayment().setId(ownerPaymentId));
when(userBankBalanceClient.getBalance(anchorUserId))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(0L)))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(0L)))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(1500L)));
when(userBankBalanceClient.getBalance(ownerUserId))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(0L)))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(375L)));
when(userBankBalanceClient.incr(any())).thenReturn(ResultResponse.success(Boolean.TRUE));
when(userBankRunningWaterClient.add(any())).thenReturn(ResultResponse.success());
when(teamSalaryPaymentService.mapPolicy(anchorPaymentId)).thenReturn(new HashMap<>());
when(teamSalaryPaymentService.mapPolicy(ownerPaymentId)).thenReturn(new HashMap<>());
List<TeamManualSalaryPaymentResultDTO> results = service.pay(new TeamManualSalaryPaymentCmd()
.setSysOrigin(sysOrigin)
.setCountryCode(countryCode)
.setUserIds(List.of(anchorUserId)));
assertEquals(2, results.size());
assertEquals(new BigDecimal("15.00"), results.stream()
.filter(result -> Objects.equals(result.getUserId(), anchorUserId))
.findFirst()
.orElseThrow()
.getIssuedSalary());
assertEquals(new BigDecimal("3.75"), results.stream()
.filter(result -> Objects.equals(result.getUserId(), ownerUserId))
.findFirst()
.orElseThrow()
.getIssuedSalary());
ArgumentCaptor<TeamSalaryPaymentDetails> anchorDetailsCaptor =
ArgumentCaptor.forClass(TeamSalaryPaymentDetails.class);
verify(teamSalaryPaymentService).paymentSalary(eq(anchorPaymentId),
anchorDetailsCaptor.capture());
assertEquals(anchorUserId, anchorDetailsCaptor.getValue().getTeamMemberId());
assertEquals(Boolean.TRUE, anchorDetailsCaptor.getValue().getAnchor());
ArgumentCaptor<TeamSalaryPaymentDetails> ownerDetailsCaptor =
ArgumentCaptor.forClass(TeamSalaryPaymentDetails.class);
verify(teamSalaryPaymentService).paymentSalary(eq(ownerPaymentId),
ownerDetailsCaptor.capture());
assertEquals(anchorUserId, ownerDetailsCaptor.getValue().getTeamMemberId());
assertEquals(ownerUserId, ownerDetailsCaptor.getValue().getTeamOwnId());
assertEquals(Boolean.FALSE, ownerDetailsCaptor.getValue().getAnchor());
assertEquals(new BigDecimal("3.75"), ownerDetailsCaptor.getValue().getSalary());
}
@Test
void payShouldNotRepeatAgentSalaryWhenTargetEntitlementAlreadyPaid() {
RedisService redisService = mock(RedisService.class);
TeamMemberService teamMemberService = mock(TeamMemberService.class);
TeamProfileService teamProfileService = mock(TeamProfileService.class);
UserBankBalanceClient userBankBalanceClient = mock(UserBankBalanceClient.class);
TeamMemberTargetService teamMemberTargetService = mock(TeamMemberTargetService.class);
TeamSalaryPaymentService teamSalaryPaymentService = mock(TeamSalaryPaymentService.class);
TeamPolicyManagerService teamPolicyManagerService = mock(TeamPolicyManagerService.class);
UserBankRunningWaterClient userBankRunningWaterClient = mock(UserBankRunningWaterClient.class);
TeamManualSalaryPaymentClientServiceImpl service = new TeamManualSalaryPaymentClientServiceImpl(
redisService, teamMemberService, teamProfileService, userBankBalanceClient,
teamMemberTargetService, teamSalaryPaymentService, teamPolicyManagerService,
userBankRunningWaterClient);
String sysOrigin = "LIKEI";
String countryCode = "PH";
String region = "2046067717605224449";
Long anchorUserId = 2054591808909930498L;
Long ownerUserId = 2057118411766427649L;
Long teamId = 2054614353419759617L;
Integer billBelong = TeamBillCycleUtils.getCalcBillBelong();
String ownerPaymentId = ownerUserId + "_" + billBelong;
TeamMemberTarget target = new TeamMemberTarget()
.setId("target-id")
.setTimeId(2056024761217118210L)
.setSysOrigin(sysOrigin)
.setRegion(region)
.setCountryCode(countryCode)
.setTeamId(teamId)
.setUserId(anchorUserId)
.setBillBelong(billBelong)
.setDailyTargets(List.of(new TeamMemberTargetIndex()
.setOwnOnlineTime(717L)
.setAcceptGiftValue(15151819L)));
TeamSalaryPayment ownerPayment = new TeamSalaryPayment()
.setId(ownerPaymentId)
.setUserId(ownerUserId)
.setDateNumber(billBelong)
.setSalarySendDetails(List.of(new TeamSalaryPaymentDetails()
.setTeamOwnId(ownerUserId)
.setTeamMemberId(anchorUserId)
.setTeamRegionId(region)
.setPolicyLevel(6)
.setAnchor(Boolean.FALSE)
.setSalary(new BigDecimal("3.75"))
.setRecycled(Boolean.FALSE)));
TeamPolicyManager policyManager = new TeamPolicyManager()
.setSysOrigin(sysOrigin)
.setRegion(region)
.setCountryCode(countryCode)
.setPolicy(List.of(new TeamPolicy()
.setLevel(6)
.setTarget(8810000L)
.setOnlineTime(10L)
.setEffectiveDay(0)
.setMemberSalary(BigDecimal.ZERO.setScale(2))
.setOwnSalary(new BigDecimal("3.75"))
.setTotalSalary(new BigDecimal("3.75"))));
when(redisService.lock(anyString(), anyLong())).thenReturn(true);
when(teamMemberTargetService.listManualSalaryTargets(eq(sysOrigin), eq(countryCode), anySet(),
eq(billBelong))).thenReturn(List.of(target));
when(teamMemberService.mapByMemberIds(anySet())).thenReturn(Map.of(anchorUserId,
new TeamMember().setMemberId(anchorUserId).setTeamId(teamId)
.setRole(TeamMemberRole.MEMBER)));
when(teamProfileService.mapProfileByIds(anySet())).thenReturn(Map.of(teamId,
new TeamProfile().setId(teamId).setOwnUserId(ownerUserId).setRegion(region)));
when(teamSalaryPaymentService.listByPaymentOrRecipientUserIdsAndDateNumbers(eq(sysOrigin),
anySet(), anySet())).thenReturn(List.of(ownerPayment));
when(teamPolicyManagerService.getReleaseByRegionAndType(sysOrigin, region, null, countryCode))
.thenReturn(policyManager);
when(userBankBalanceClient.getBalance(anchorUserId))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(0L)));
List<TeamManualSalaryPaymentResultDTO> results = service.pay(new TeamManualSalaryPaymentCmd()
.setSysOrigin(sysOrigin)
.setCountryCode(countryCode)
.setUserIds(List.of(anchorUserId)));
assertEquals(0, results.size());
verify(teamSalaryPaymentService, never()).createIfAbsent(anyLong(), any(), anyString());
verify(teamSalaryPaymentService, never()).paymentSalary(anyString(), any());
}
private Object paymentSummary(BigDecimal totalIssuedSalary, BigDecimal memberIssuedSalary, private Object paymentSummary(BigDecimal totalIssuedSalary, BigDecimal memberIssuedSalary,
BigDecimal agentIssuedSalary) throws Exception { BigDecimal agentIssuedSalary) throws Exception {
Class<?> summaryClass = Class.forName( Class<?> summaryClass = Class.forName(