Merge branch 'test'

# Conflicts:
#	rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/TeamBillTask.java
#	rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImpl.java
#	rc-service/rc-service-other/other-inner-endpoint/src/test/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImplTest.java
This commit is contained in:
hy001 2026-06-01 19:58:08 +08:00
commit 7f7649d6bd
11 changed files with 557 additions and 291 deletions

View File

@ -28,16 +28,16 @@ public class TeamManualSalaryPaymentCmd extends Command {
*/ */
private String countryCode; private String countryCode;
/**
* 工资账期为空时使用当前整月账期.
*/
private Integer billBelong;
/** /**
* 指定发放用户为空时按国家全量发放. * 指定发放用户为空时按国家全量发放.
*/ */
private List<Long> userIds; private List<Long> userIds;
/**
* 月度账期格式 yyyyMM. 为空时使用当前月.
*/
private Integer billBelong;
/** /**
* 后台操作人. * 后台操作人.
*/ */

View File

@ -27,16 +27,16 @@ public class TeamManualSalaryPaymentQryCmd extends PageCommand {
*/ */
private String countryCode; private String countryCode;
/**
* 工资账期为空时使用当前整月账期.
*/
private Integer billBelong;
/** /**
* 用户 id. * 用户 id.
*/ */
private Long userId; private Long userId;
/**
* 月度账期格式 yyyyMM. 为空时使用当前月.
*/
private Integer billBelong;
/** /**
* 排序字段. * 排序字段.
*/ */

View File

@ -229,6 +229,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

@ -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,9 +87,9 @@ 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. 缓存结果
@ -101,34 +98,57 @@ public class TeamBdMemberBillListQryExe {
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);
@ -180,18 +200,15 @@ 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)
@ -284,15 +301,13 @@ public class TeamBdMemberBillListQryExe {
.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) {
@ -318,9 +333,7 @@ public class TeamBdMemberBillListQryExe {
/** /**
* 创建空结果. * 创建空结果.
*/ */
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);
@ -338,6 +351,25 @@ public class TeamBdMemberBillListQryExe {
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)) {
@ -449,10 +481,17 @@ public class TeamBdMemberBillListQryExe {
} }
List<TeamMemberTarget> targetList = teamMemberTargetMap.get(teamId); List<TeamMemberTarget> targetList = teamMemberTargetMap.get(teamId);
if (CollectionUtils.isNotEmpty(targetList)) {
List<TeamBillMemberTarget> memberTargets = TeamBillCycleUtils.calculatorPolicyTargetWithMap(targetList, policyManagerMap); List<TeamBillMemberTarget> memberTargets = TeamBillCycleUtils.calculatorPolicyTargetWithMap(targetList, policyManagerMap);
return ownTotalSalary(memberTargets); 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) {
return teamBillMemberTargets.stream().map(TeamBillMemberTarget::getTotalSalary) return teamBillMemberTargets.stream().map(TeamBillMemberTarget::getTotalSalary)
.reduce(BigDecimal.ZERO, BigDecimal::add) .reduce(BigDecimal.ZERO, BigDecimal::add)
@ -483,9 +522,6 @@ public class TeamBdMemberBillListQryExe {
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);

View File

@ -22,6 +22,10 @@ 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.rds.entity.props.PropsBackpack;
import com.red.circle.other.infra.database.rds.entity.props.PropsNobleVipAbility;
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.entity.live.RoomSetting;
import com.red.circle.other.infra.database.mongo.service.live.RoomProfileManagerService; 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.database.rds.service.props.PropsVipActualEquityService;
@ -48,9 +52,12 @@ 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.sql.Timestamp;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Objects; 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;
@ -77,6 +84,8 @@ public class PropsPurchasingCmdExe {
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) {
@ -125,6 +134,8 @@ public class PropsPurchasingCmdExe {
log.warn("查询赠送参数信息:{}"+JSON.toJSON(commodity)); log.warn("查询赠送参数信息:{}"+JSON.toJSON(commodity));
nobleVipAbility = commodity.getNobleVipAbility(); nobleVipAbility = commodity.getNobleVipAbility();
expireSupersededNobleVipMaterials(cmd.acceptUser(), commodity, nobleVipAbility,
TimestampUtils.now());
if (nobleVipAbility.cardId() != null) { if (nobleVipAbility.cardId() != null) {
propsStoreGateway.shippingProductProps(new ProductProps() propsStoreGateway.shippingProductProps(new ProductProps()
.setInitiateUserId(cmd.requiredReqUserId()) .setInitiateUserId(cmd.requiredReqUserId())
@ -189,6 +200,65 @@ public class PropsPurchasingCmdExe {
} }
} }
private void expireSupersededNobleVipMaterials(Long userId, 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) { private void sendGiveAwayNotice(PropsPurchasingCmd cmd, PropsStoreCommodity commodity) {
UserProfileDTO userProfileDTO = userProfileAppConvertor.toUserProfileDTO(userProfileGateway.getByUserId(cmd.requiredReqUserId())); UserProfileDTO userProfileDTO = userProfileAppConvertor.toUserProfileDTO(userProfileGateway.getByUserId(cmd.requiredReqUserId()));

View File

@ -70,7 +70,6 @@ public class TeamBillTask {
consumeProcessPayOutBill(); consumeProcessPayOutBill();
log.warn("结束账期切换"); log.warn("结束账期切换");
} }
public void processTeamBillRetry() { public void processTeamBillRetry() {
if (BILL_CYCLE_SWITCH_PAUSED) { if (BILL_CYCLE_SWITCH_PAUSED) {
log.warn("process_team_bill_retry skipped: bill cycle switch is temporarily paused"); log.warn("process_team_bill_retry skipped: bill cycle switch is temporarily paused");

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

@ -132,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(),
@ -143,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);
@ -242,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);
@ -339,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());
} }
@ -367,30 +376,31 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
TeamPolicyManager policyManager = getPolicyManager(target.getSysOrigin(), region, countryCode, TeamPolicyManager policyManager = getPolicyManager(target.getSysOrigin(), region, countryCode,
policyCache); policyCache);
TeamBillMemberTarget billTarget = calculateBillTarget(target, policyManager); TeamBillMemberTarget billTarget = calculateBillTarget(target, policyManager);
boolean includeAgentSalary = isAgentMember(member, teamProfile, target.getUserId()); boolean agentMember = isAgentMember(member, teamProfile, target.getUserId());
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(), includeAgentSalary);
BigDecimal expectedMemberSalary = salaryValue(billTarget.getMemberSalary()); BigDecimal expectedMemberSalary = salaryValue(billTarget.getMemberSalary());
BigDecimal expectedAgentSalary = includeAgentSalary ? salaryValue(billTarget.getOwnSalary()) BigDecimal expectedAgentSalary = Objects.nonNull(teamProfile.getOwnUserId())
? salaryValue(billTarget.getOwnSalary())
: BigDecimal.ZERO.setScale(2, RoundingMode.DOWN); : BigDecimal.ZERO.setScale(2, RoundingMode.DOWN);
BigDecimal expectedSalary = expectedMemberSalary.add(expectedAgentSalary) BigDecimal expectedSalary = expectedMemberSalary.add(expectedAgentSalary)
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
BigDecimal payableSalary = expectedSalary.subtract(receivedPaymentSummary.getTotalIssuedSalary()) PaymentSummary receivedPaymentSummary = summarizeTargetEntitlementPayments(cyclePayments,
.setScale(2, RoundingMode.DOWN); 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, expectedMemberSalary, expectedAgentSalary, payableSalary); billTarget, receivedPaymentSummary, expectedMemberSalary, expectedAgentSalary);
List<PolicyPayment> policyPayments = buildSettlementPayments(policyManager, billTarget, List<PolicyPayment> policyPayments = buildSettlementPayments(policyManager, billTarget,
cyclePayments, target.getUserId(), region, receivedPaymentSummary, expectedMemberSalary, cyclePayments, target.getUserId(), region, receivedPaymentSummary, currentLevelSettled);
expectedAgentSalary, payableSalary, currentLevelSettled);
TeamManualSalaryPaymentDTO dto = new TeamManualSalaryPaymentDTO() TeamManualSalaryPaymentDTO dto = new TeamManualSalaryPaymentDTO()
.setId(target.getId()) .setId(target.getId())
@ -401,11 +411,11 @@ 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())
.setAgentMember(includeAgentSalary) .setAgentMember(agentMember)
.setExpectedSalary(expectedSalary) .setExpectedSalary(expectedSalary)
.setExpectedMemberSalary(expectedMemberSalary) .setExpectedMemberSalary(expectedMemberSalary)
.setExpectedAgentSalary(expectedAgentSalary) .setExpectedAgentSalary(expectedAgentSalary)
@ -493,19 +503,23 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
.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 memberExpected, String region, PaymentSummary paymentSummary, boolean currentLevelSettled) {
BigDecimal agentExpected, BigDecimal payableSalary, 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 BigDecimal memberExpected = salaryValue(billTarget.getMemberSalary());
? payableSalary.setScale(2, RoundingMode.DOWN) BigDecimal agentExpected = salaryValue(billTarget.getOwnSalary());
: BigDecimal.ZERO.setScale(2, RoundingMode.DOWN);
BigDecimal memberMissing = memberExpected.subtract(paymentSummary.getMemberIssuedSalary()) BigDecimal memberMissing = memberExpected.subtract(paymentSummary.getMemberIssuedSalary())
.setScale(2, RoundingMode.DOWN); .setScale(2, RoundingMode.DOWN);
BigDecimal agentMissing = agentExpected.subtract(paymentSummary.getAgentIssuedSalary()) BigDecimal agentMissing = agentExpected.subtract(paymentSummary.getAgentIssuedSalary())
@ -516,35 +530,21 @@ 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 (memberMissing.compareTo(BigDecimal.ZERO) > 0) {
if (!memberHasSettlementRecord && memberExpected.compareTo(BigDecimal.ZERO) > 0) { payments.add(new PolicyPayment(policy, billTarget.getLevel(), memberMissing, Boolean.TRUE));
} else if (!memberHasSettlementRecord && memberExpected.compareTo(BigDecimal.ZERO) > 0) {
payments.add(new PolicyPayment(policy, billTarget.getLevel(), payments.add(new PolicyPayment(policy, billTarget.getLevel(),
BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.TRUE)); BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.TRUE));
} }
if (!agentHasSettlementRecord && agentExpected.compareTo(BigDecimal.ZERO) > 0) { if (agentMissing.compareTo(BigDecimal.ZERO) > 0) {
payments.add(new PolicyPayment(policy, billTarget.getLevel(), agentMissing, Boolean.FALSE));
} else if (!agentHasSettlementRecord && agentExpected.compareTo(BigDecimal.ZERO) > 0) {
payments.add(new PolicyPayment(policy, billTarget.getLevel(), payments.add(new PolicyPayment(policy, billTarget.getLevel(),
BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.FALSE)); BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.FALSE));
} }
return payments; return payments;
} }
if (memberMissing.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal issueSalary = memberMissing.min(remainingPayable).setScale(2, RoundingMode.DOWN);
if (issueSalary.compareTo(BigDecimal.ZERO) > 0) {
payments.add(new PolicyPayment(policy, billTarget.getLevel(), issueSalary, Boolean.TRUE));
remainingPayable = remainingPayable.subtract(issueSalary).setScale(2, RoundingMode.DOWN);
}
}
if (agentMissing.compareTo(BigDecimal.ZERO) > 0
&& remainingPayable.compareTo(BigDecimal.ZERO) > 0) {
BigDecimal issueSalary = agentMissing.min(remainingPayable).setScale(2, RoundingMode.DOWN);
if (issueSalary.compareTo(BigDecimal.ZERO) > 0) {
payments.add(new PolicyPayment(policy, billTarget.getLevel(), issueSalary, Boolean.FALSE));
}
}
return payments;
}
private TeamPolicy findPolicy(TeamPolicyManager policyManager, Integer level) { private TeamPolicy findPolicy(TeamPolicyManager policyManager, Integer level) {
if (Objects.isNull(policyManager) || CollectionUtils.isEmpty(policyManager.getPolicy()) if (Objects.isNull(policyManager) || CollectionUtils.isEmpty(policyManager.getPolicy())
|| Objects.isNull(level)) { || Objects.isNull(level)) {
@ -557,22 +557,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 expectedMemberSalary, String region, TeamBillMemberTarget billTarget, PaymentSummary paymentSummary,
BigDecimal expectedAgentSalary, BigDecimal payableSalary) { 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,
expectedMemberSalary) expectedMemberSalary, paymentSummary.getMemberIssuedSalary())
&& isRoleSettled(cyclePayments, userId, region, billTarget.getLevel(), Boolean.FALSE, && isRoleSettled(cyclePayments, userId, region, billTarget.getLevel(), Boolean.FALSE,
expectedAgentSalary); 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,
@ -584,33 +587,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 summarizeReceivedPayments(List<TeamSalaryPayment> cyclePayments, private PaymentSummary summarizeTargetEntitlementPayments(List<TeamSalaryPayment> cyclePayments,
Long receiverUserId, boolean includeAgentSalary) { Long targetUserId, BigDecimal expectedMemberSalary, BigDecimal expectedAgentSalary) {
if (Objects.isNull(receiverUserId)) { if (Objects.isNull(targetUserId)) {
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()))
.filter(detail -> includeAgentSalary || Boolean.TRUE.equals(detail.getAnchor()))
.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,
@ -622,17 +631,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))
@ -641,38 +639,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)) {
@ -735,6 +701,13 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
teamSalaryPaymentService.updatePolicyMap(paymentId, policyMap); teamSalaryPaymentService.updatePolicyMap(paymentId, policyMap);
} }
private Long getPaymentReceiverUserId(ManualSalaryRow row, PolicyPayment payment) {
if (Objects.isNull(row) || Objects.isNull(payment)) {
return null;
}
return payment.isAnchor() ? row.getUserId() : row.getTeamOwnId();
}
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());
@ -757,10 +730,6 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
return billBelong; return billBelong;
} }
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

@ -1,6 +1,7 @@
package com.red.circle.other.app.inner.service.team.impl; package com.red.circle.other.app.inner.service.team.impl;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anySet; import static org.mockito.ArgumentMatchers.anySet;
@ -27,8 +28,8 @@ 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.TeamMemberTargetIndex;
import com.red.circle.other.inner.enums.team.TeamMemberRole; import com.red.circle.other.inner.enums.team.TeamMemberRole;
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;
import com.red.circle.wallet.inner.endpoint.bank.UserBankBalanceClient; import com.red.circle.wallet.inner.endpoint.bank.UserBankBalanceClient;
@ -39,6 +40,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,14 +75,16 @@ class TeamManualSalaryPaymentClientServiceImplTest {
new BigDecimal("9.50")); new BigDecimal("9.50"));
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, false);
new BigDecimal("35.00"), new BigDecimal("8.75"), new BigDecimal("12.25"), 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,8 +105,7 @@ class TeamManualSalaryPaymentClientServiceImplTest {
new BigDecimal("9.00")); new BigDecimal("9.00"));
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, false);
new BigDecimal("35.00"), new BigDecimal("8.75"), new BigDecimal("-1.25"), 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),
@ -111,6 +114,16 @@ class TeamManualSalaryPaymentClientServiceImplTest {
"salary")); "salary"));
} }
@Test
void resolveBillBelongShouldRejectInvalidMonth() {
TeamManualSalaryPaymentClientServiceImpl service =
new TeamManualSalaryPaymentClientServiceImpl(
null, null, null, null, null, null, null, null);
assertThrows(RuntimeException.class,
() -> ReflectionTestUtils.invokeMethod(service, "resolveBillBelong", 202613));
}
@Test @Test
void payShouldReturnTopUpDetailsWhenCurrentLevelRecordExistsButSalaryIsInsufficient() { void payShouldReturnTopUpDetailsWhenCurrentLevelRecordExistsButSalaryIsInsufficient() {
RedisService redisService = mock(RedisService.class); RedisService redisService = mock(RedisService.class);
@ -194,7 +207,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<>());
@ -207,18 +221,18 @@ 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 @Test
void payShouldNotRepeatAgentSalaryAlreadyPaidToTeamOwner() { void payShouldIssueMemberSalaryToAnchorAndAgentSalaryToTeamOwner() {
RedisService redisService = mock(RedisService.class); RedisService redisService = mock(RedisService.class);
TeamMemberService teamMemberService = mock(TeamMemberService.class); TeamMemberService teamMemberService = mock(TeamMemberService.class);
TeamProfileService teamProfileService = mock(TeamProfileService.class); TeamProfileService teamProfileService = mock(TeamProfileService.class);
@ -235,85 +249,186 @@ class TeamManualSalaryPaymentClientServiceImplTest {
String sysOrigin = "LIKEI"; String sysOrigin = "LIKEI";
String countryCode = "PH"; String countryCode = "PH";
String region = "2046067717605224449"; String region = "2046067717605224449";
Long userId = 2052023053021409281L; Long anchorUserId = 2054591808909930498L;
Long ownerUserId = 2051169779338768386L; Long ownerUserId = 2057118411766427649L;
Long teamId = 2051672510944047106L; Long teamId = 2054614353419759617L;
Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); Integer billBelong = TeamBillCycleUtils.getCalcBillBelong();
String paymentId = userId + "_" + billBelong; String anchorPaymentId = anchorUserId + "_" + billBelong;
String ownerPaymentId = ownerUserId + "_" + billBelong;
TeamMemberTarget target = new TeamMemberTarget() TeamMemberTarget target = new TeamMemberTarget()
.setId("target-id") .setId("target-id")
.setTimeId(1779030072251003137L) .setTimeId(2056024761217118210L)
.setSysOrigin(sysOrigin) .setSysOrigin(sysOrigin)
.setRegion(region) .setRegion(region)
.setCountryCode(countryCode) .setCountryCode(countryCode)
.setTeamId(teamId) .setTeamId(teamId)
.setUserId(userId) .setUserId(anchorUserId)
.setBillBelong(billBelong) .setBillBelong(billBelong)
.setDailyTargets(List.of(new TeamMemberTargetIndex() .setDailyTargets(List.of(new TeamMemberTargetIndex()
.setOwnOnlineTime(4586L) .setOwnOnlineTime(717L)
.setAcceptGiftValue(2104843L))); .setAcceptGiftValue(15151819L)));
TeamSalaryPayment existingPayment = new TeamSalaryPayment() TeamPolicyManager policyManager = new TeamPolicyManager()
.setId(paymentId) .setSysOrigin(sysOrigin)
.setUserId(userId) .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) .setDateNumber(billBelong)
.setSalarySendDetails(List.of( .setSalarySendDetails(List.of(new TeamSalaryPaymentDetails()
new TeamSalaryPaymentDetails()
.setTeamMemberId(userId)
.setTeamOwnId(ownerUserId) .setTeamOwnId(ownerUserId)
.setTeamMemberId(anchorUserId)
.setTeamRegionId(region) .setTeamRegionId(region)
.setPolicyLevel(4) .setPolicyLevel(6)
.setAnchor(Boolean.TRUE)
.setSalary(new BigDecimal("8.00"))
.setRecycled(Boolean.FALSE),
new TeamSalaryPaymentDetails()
.setTeamMemberId(userId)
.setTeamOwnId(ownerUserId)
.setTeamRegionId(region)
.setPolicyLevel(4)
.setAnchor(Boolean.FALSE) .setAnchor(Boolean.FALSE)
.setSalary(new BigDecimal("2.00")) .setSalary(new BigDecimal("3.75"))
.setRecycled(Boolean.FALSE))); .setRecycled(Boolean.FALSE)));
TeamPolicyManager policyManager = new TeamPolicyManager() TeamPolicyManager policyManager = new TeamPolicyManager()
.setSysOrigin(sysOrigin) .setSysOrigin(sysOrigin)
.setRegion(region) .setRegion(region)
.setCountryCode(countryCode) .setCountryCode(countryCode)
.setPolicy(List.of(new TeamPolicy() .setPolicy(List.of(new TeamPolicy()
.setLevel(4) .setLevel(6)
.setTarget(2000000L) .setTarget(8810000L)
.setOnlineTime(10L) .setOnlineTime(10L)
.setEffectiveDay(0) .setEffectiveDay(0)
.setMemberSalary(new BigDecimal("8.00")) .setMemberSalary(BigDecimal.ZERO.setScale(2))
.setOwnSalary(new BigDecimal("2.00")) .setOwnSalary(new BigDecimal("3.75"))
.setTotalSalary(new BigDecimal("10.00")))); .setTotalSalary(new BigDecimal("3.75"))));
when(redisService.lock(anyString(), anyLong())).thenReturn(true); when(redisService.lock(anyString(), anyLong())).thenReturn(true);
when(teamMemberTargetService.listManualSalaryTargets(eq(sysOrigin), eq(countryCode), anySet(), when(teamMemberTargetService.listManualSalaryTargets(eq(sysOrigin), eq(countryCode), anySet(),
eq(billBelong))).thenReturn(List.of(target)); eq(billBelong))).thenReturn(List.of(target));
when(teamMemberService.mapByMemberIds(anySet())).thenReturn(Map.of(userId, when(teamMemberService.mapByMemberIds(anySet())).thenReturn(Map.of(anchorUserId,
new TeamMember().setMemberId(userId).setTeamId(teamId))); new TeamMember().setMemberId(anchorUserId).setTeamId(teamId)
.setRole(TeamMemberRole.MEMBER)));
when(teamProfileService.mapProfileByIds(anySet())).thenReturn(Map.of(teamId, when(teamProfileService.mapProfileByIds(anySet())).thenReturn(Map.of(teamId,
new TeamProfile().setId(teamId).setOwnUserId(ownerUserId).setRegion(region))); new TeamProfile().setId(teamId).setOwnUserId(ownerUserId).setRegion(region)));
when(teamSalaryPaymentService.listByPaymentOrRecipientUserIdsAndDateNumbers(eq(sysOrigin), when(teamSalaryPaymentService.listByPaymentOrRecipientUserIdsAndDateNumbers(eq(sysOrigin),
anySet(), anySet())).thenReturn(List.of(existingPayment)); anySet(), anySet())).thenReturn(List.of(ownerPayment));
when(teamPolicyManagerService.getReleaseByRegionAndType(sysOrigin, region, null, countryCode)) when(teamPolicyManagerService.getReleaseByRegionAndType(sysOrigin, region, null, countryCode))
.thenReturn(policyManager); .thenReturn(policyManager);
when(userBankBalanceClient.getBalance(userId)) when(userBankBalanceClient.getBalance(anchorUserId))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(0L))); .thenReturn(ResultResponse.success(BankBalanceDTO.of(0L)));
List<TeamManualSalaryPaymentResultDTO> results = service.pay(new TeamManualSalaryPaymentCmd() List<TeamManualSalaryPaymentResultDTO> results = service.pay(new TeamManualSalaryPaymentCmd()
.setSysOrigin(sysOrigin) .setSysOrigin(sysOrigin)
.setCountryCode(countryCode) .setCountryCode(countryCode)
.setUserIds(List.of(userId))); .setUserIds(List.of(anchorUserId)));
assertEquals(0, results.size()); assertEquals(0, results.size());
verify(teamSalaryPaymentService, never()).createIfAbsent(anyLong(), any(), anyString()); verify(teamSalaryPaymentService, never()).createIfAbsent(anyLong(), any(), anyString());
verify(userBankBalanceClient, never()).incr(any());
verify(userBankRunningWaterClient, never()).add(any());
verify(teamSalaryPaymentService, never()).paymentSalary(anyString(), any()); verify(teamSalaryPaymentService, never()).paymentSalary(anyString(), any());
} }
@Test @Test
void payShouldOnlyIssueMemberSalaryForNormalAnchor() { void payShouldNotRepeatTargetEntitlementsAlreadyPaidInTargetPaymentDoc() {
RedisService redisService = mock(RedisService.class); RedisService redisService = mock(RedisService.class);
TeamMemberService teamMemberService = mock(TeamMemberService.class); TeamMemberService teamMemberService = mock(TeamMemberService.class);
TeamProfileService teamProfileService = mock(TeamProfileService.class); TeamProfileService teamProfileService = mock(TeamProfileService.class);
@ -337,41 +452,54 @@ class TeamManualSalaryPaymentClientServiceImplTest {
String paymentId = userId + "_" + billBelong; String paymentId = userId + "_" + billBelong;
TeamMemberTarget target = manualSalaryTarget(sysOrigin, countryCode, region, userId, teamId, TeamMemberTarget target = manualSalaryTarget(sysOrigin, countryCode, region, userId, teamId,
billBelong); billBelong);
TeamSalaryPayment existingPayment = new TeamSalaryPayment()
.setId(paymentId)
.setUserId(userId)
.setDateNumber(billBelong)
.setSalarySendDetails(List.of(
new TeamSalaryPaymentDetails()
.setTeamMemberId(userId)
.setTeamOwnId(ownerUserId)
.setTeamRegionId(region)
.setPolicyLevel(4)
.setAnchor(Boolean.TRUE)
.setSalary(new BigDecimal("8.00"))
.setRecycled(Boolean.FALSE),
new TeamSalaryPaymentDetails()
.setTeamMemberId(userId)
.setTeamOwnId(ownerUserId)
.setTeamRegionId(region)
.setPolicyLevel(4)
.setAnchor(Boolean.FALSE)
.setSalary(new BigDecimal("2.00"))
.setRecycled(Boolean.FALSE)));
TeamPolicyManager policyManager = manualSalaryPolicy(sysOrigin, countryCode, region); TeamPolicyManager policyManager = manualSalaryPolicy(sysOrigin, countryCode, region);
when(redisService.lock(anyString(), anyLong())).thenReturn(true); when(redisService.lock(anyString(), anyLong())).thenReturn(true);
when(teamMemberTargetService.listManualSalaryTargets(eq(sysOrigin), eq(countryCode), anySet(), when(teamMemberTargetService.listManualSalaryTargets(eq(sysOrigin), eq(countryCode), anySet(),
eq(billBelong))).thenReturn(List.of(target)); eq(billBelong))).thenReturn(List.of(target));
when(teamMemberService.mapByMemberIds(anySet())).thenReturn(Map.of(userId, when(teamMemberService.mapByMemberIds(anySet())).thenReturn(Map.of(userId,
new TeamMember().setMemberId(userId).setTeamId(teamId).setRole(TeamMemberRole.MEMBER))); new TeamMember().setMemberId(userId).setTeamId(teamId)
.setRole(TeamMemberRole.MEMBER)));
when(teamProfileService.mapProfileByIds(anySet())).thenReturn(Map.of(teamId, when(teamProfileService.mapProfileByIds(anySet())).thenReturn(Map.of(teamId,
new TeamProfile().setId(teamId).setOwnUserId(ownerUserId).setRegion(region))); new TeamProfile().setId(teamId).setOwnUserId(ownerUserId).setRegion(region)));
when(teamSalaryPaymentService.listByPaymentOrRecipientUserIdsAndDateNumbers(eq(sysOrigin), when(teamSalaryPaymentService.listByPaymentOrRecipientUserIdsAndDateNumbers(eq(sysOrigin),
anySet(), anySet())).thenReturn(List.of()); anySet(), anySet())).thenReturn(List.of(existingPayment));
when(teamPolicyManagerService.getReleaseByRegionAndType(sysOrigin, region, null, countryCode)) when(teamPolicyManagerService.getReleaseByRegionAndType(sysOrigin, region, null, countryCode))
.thenReturn(policyManager); .thenReturn(policyManager);
when(teamSalaryPaymentService.createIfAbsent(userId, billBelong, sysOrigin))
.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(800L)));
when(userBankBalanceClient.incr(any())).thenReturn(ResultResponse.success(Boolean.TRUE));
when(userBankRunningWaterClient.add(any())).thenReturn(ResultResponse.success());
when(teamSalaryPaymentService.mapPolicy(paymentId)).thenReturn(new HashMap<>());
List<TeamManualSalaryPaymentResultDTO> results = service.pay(new TeamManualSalaryPaymentCmd() List<TeamManualSalaryPaymentResultDTO> results = service.pay(new TeamManualSalaryPaymentCmd()
.setSysOrigin(sysOrigin) .setSysOrigin(sysOrigin)
.setCountryCode(countryCode) .setCountryCode(countryCode)
.setUserIds(List.of(userId))); .setUserIds(List.of(userId)));
assertEquals(1, results.size()); assertEquals(0, results.size());
assertEquals(userId, results.get(0).getUserId()); verify(teamSalaryPaymentService, never()).createIfAbsent(anyLong(), any(), anyString());
assertEquals(new BigDecimal("8.00"), results.get(0).getIssuedSalary()); verify(userBankBalanceClient, never()).incr(any());
ArgumentCaptor<TeamSalaryPaymentDetails> detailsCaptor = verify(userBankRunningWaterClient, never()).add(any());
ArgumentCaptor.forClass(TeamSalaryPaymentDetails.class); verify(teamSalaryPaymentService, never()).paymentSalary(anyString(), any());
verify(teamSalaryPaymentService).paymentSalary(eq(paymentId), detailsCaptor.capture());
assertEquals(Boolean.TRUE, detailsCaptor.getValue().getAnchor());
assertEquals(new BigDecimal("8.00"), detailsCaptor.getValue().getSalary());
} }
@Test @Test
@ -414,6 +542,7 @@ class TeamManualSalaryPaymentClientServiceImplTest {
when(teamSalaryPaymentService.createIfAbsent(userId, billBelong, sysOrigin)) when(teamSalaryPaymentService.createIfAbsent(userId, billBelong, sysOrigin))
.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(0L)))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(800L))) .thenReturn(ResultResponse.success(BankBalanceDTO.of(800L)))
.thenReturn(ResultResponse.success(BankBalanceDTO.of(800L))) .thenReturn(ResultResponse.success(BankBalanceDTO.of(800L)))