自动结算

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 Integer billBelong;
/**
* 指定发放用户为空时按国家全量发放.
*/

View File

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

View File

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

View File

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

View File

@ -30,6 +30,7 @@ import com.red.circle.tool.core.sequence.IdWorkerUtils;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.time.Duration;
import java.util.HashSet;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -96,7 +97,10 @@ public class GmVipGiftServiceImpl implements GmVipGiftService {
expireAt, now);
vipOrderRecordService.save(order);
Set<Long> previousResourceIds = vipResourceIds(currentState);
upsertUserVipState(currentState, userProfile, config, startAt, expireAt, now);
expireSupersededVipResources(userProfile.getOriginSys(), userProfile.getId(), config,
previousResourceIds, now);
giftVipResources(userProfile.getId(), config, now, cmd.getReqUserId());
GmVipGiftRecord record = buildGiftRecord(cmd, applicant, userProfile, fromLevel, config,
@ -272,6 +276,63 @@ public class GmVipGiftServiceImpl implements GmVipGiftService {
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,
Long opUser) {
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 agentOverIssuedSalary;
private BigDecimal currentSalaryBalance;
private Boolean agentMember;
private Boolean paid;
}

View File

@ -132,10 +132,10 @@ public class TestRestController extends BaseController {
teamSalaryPaymentTask.teamSalaryPaymentTaskTest();
}
@PostMapping("/processTeamMonthBill")
public void processTeamMonthBill() {
teamBillTask.processTeamBillRetry();
}
@PostMapping("/processTeamMonthBill")
public void processTeamMonthBill() {
teamBillTask.processTeamMonthBillTest();
}
@PostMapping("/activity/send/game")
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.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.framework.dto.ResultResponse;
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.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.BdSalarySettlementRecord;
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.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.service.team.BusinessDevelopmentBaseInfoService;
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.model.dto.agency.agency.TeamCounter;
import com.red.circle.other.inner.model.dto.user.UserProfileDTO;
@ -43,8 +40,6 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@ -80,8 +75,10 @@ public class TeamBdMemberBillListQryExe {
* @return 账单列表
*/
public TeamBdMemberCO execute(TeamBdMemberBillCmd cmd) {
Integer billBelong = resolveBillBelong(cmd.getBillBelong());
// 1. 缓存查询
String key = "bdcenter:" + cmd.getType() + ":" + cmd.getReqUserId();
String key = "bdcenter:" + cmd.getType() + ":" + cmd.getReqUserId() + ":" + billBelong;
TeamBdMemberCO cached = redisService.getStringToObject(key, TeamBdMemberCO.class);
if (cached != null) {
return cached;
@ -90,45 +87,68 @@ public class TeamBdMemberBillListQryExe {
// 2. 根据类型执行不同的查询逻辑
TeamBdMemberCO result;
if ("BD".equals(cmd.getType())) {
result = queryBdTeamData(cmd.getReqUserId());
result = queryBdTeamData(cmd.getReqUserId(), billBelong);
} else {
result = queryBdLeaderTeamData(cmd.getReqUserId());
result = queryBdLeaderTeamData(cmd.getReqUserId(), billBelong);
}
// 3. 缓存结果
redisService.setString(key, JSON.toJSONString(result), 2, TimeUnit.MINUTES);
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 团队列表.
*/
private TeamBdMemberCO queryBdTeamData(Long bdUserId) {
private TeamBdMemberCO queryBdTeamData(Long bdUserId, Integer currentBillBelong) {
// 1. 查询该 BD 的所有团队
List<BusinessDevelopmentTeam> bdTeamList = businessDevelopmentTeamService.listByUserId(bdUserId);
if (CollectionUtils.isEmpty(bdTeamList)) {
return createEmptyResult();
return createEmptyResult(currentBillBelong);
}
// 2. 获取当前账单周期
Integer currentBillBelong = TeamBillCycleUtils.getCalcBillBelong();
// 3. 获取团队ID集合
Set<Long> teamIdSet = bdTeamList.stream()
.map(BusinessDevelopmentTeam::getTeamId)
.collect(Collectors.toSet());
// 获取所有区域的政策配置
Map<Long, TeamBillCycle> billCycleMap = teamBillCycleService.mapByTeamIds2(
teamIdSet, currentBillBelong, TeamBillCycleStatus.UNPAID);
Map<Long, TeamBillCycle> billCycleMap = getBillCycleMap(teamIdSet, currentBillBelong);
Map<String, TeamPolicyManager> regionPolicyMap = teamPolicyManagerService.mapRegionReleaseWithCountry(
billCycleMap.values().stream().map(TeamBillCycle::getRegion).collect(Collectors.toSet()));
// 4. 查询当前周期的账单
Map<Long, List<TeamMemberTarget>> memberBillTarget =
teamMemberTargetService.mapMemberBillTarget(teamIdSet, currentBillBelong);
Map<Long, List<TeamMemberTarget>> memberBillTarget = getMemberBillTargetMap(teamIdSet, currentBillBelong);
// 5. 获取团队Profile信息
Map<Long, TeamProfile> teamProfileMap = teamProfileService.mapProfileByIds(teamIdSet);
@ -168,9 +188,9 @@ public class TeamBdMemberBillListQryExe {
result.setTotalSalary(totalSalary);
result.setTotalRecharge(totalRecharge);
result.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(currentBillBelong));
// 11. 计算当前账单的 BD 工资信息
BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong,
BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong,
bdTeamList.size(), totalSalary, totalRecharge);
result.setBdHistoryCO(bdHistoryCO);
@ -180,27 +200,24 @@ public class TeamBdMemberBillListQryExe {
/**
* 查询 BD Leader 的团队数据返回 BD 列表每个 BD 汇总其所有团队.
*/
private TeamBdMemberCO queryBdLeaderTeamData(Long bdLeaderUserId) {
private TeamBdMemberCO queryBdLeaderTeamData(Long bdLeaderUserId, Integer currentBillBelong) {
// 1. 查询该 Leader 下的所有 BD
List<BusinessDevelopmentBaseInfo> bdList =
List<BusinessDevelopmentBaseInfo> bdList =
businessDevelopmentBaseInfoService.listBdByLeadUserId(bdLeaderUserId);
if (CollectionUtils.isEmpty(bdList)) {
return createEmptyResult();
return createEmptyResult(currentBillBelong);
}
// 2. 获取当前账单周期
Integer currentBillBelong = TeamBillCycleUtils.getCalcBillBelong();
// 3. 收集所有 BD 的用户ID
Set<Long> bdUserIdSet = bdList.stream()
.map(BusinessDevelopmentBaseInfo::getUserId)
.collect(Collectors.toSet());
// 4. 批量查询所有 BD 的用户信息
Map<Long, UserProfileDTO> bdUserProfileMap = userProfileAppConvertor.toMapUserProfileDTO(
userProfileGateway.mapByUserIds(bdUserIdSet));
// 5. 为每个 BD 构建汇总数据
List<TeamBdMemberBillCO> memberBillList = bdList.stream()
.map(bdInfo -> buildBdSummaryBillCO(bdInfo, bdUserProfileMap, currentBillBelong))
@ -213,7 +230,7 @@ public class TeamBdMemberBillListQryExe {
BigDecimal totalRecharge = memberBillList.stream()
.map(TeamBdMemberBillCO::getTeamRechargeAmount)
.reduce(BigDecimal.ZERO, BigDecimal::add);
// 7. 计算总的 Agent 数量所有 BD Agent 总和
Integer totalAgentCount = memberBillList.stream()
.map(TeamBdMemberBillCO::getTeamMemberCount)
@ -228,9 +245,9 @@ public class TeamBdMemberBillListQryExe {
result.setTotalSalary(totalSalary);
result.setTotalRecharge(totalRecharge);
result.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(currentBillBelong));
// 9. 计算当前账单的 BD Leader 工资信息使用总的 Agent 数量
BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong,
BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong,
totalAgentCount, totalSalary, totalRecharge);
bdHistoryCO.setBdNumber(bdList.size());
result.setBdHistoryCO(bdHistoryCO);
@ -245,9 +262,9 @@ public class TeamBdMemberBillListQryExe {
BusinessDevelopmentBaseInfo bdInfo,
Map<Long, UserProfileDTO> bdUserProfileMap,
Integer currentBillBelong) {
TeamBdMemberBillCO co = new TeamBdMemberBillCO();
// 1. 设置 BD 用户信息这里的 Agent 字段实际上是 BD 信息
UserProfileDTO bdUserProfile = bdUserProfileMap.get(bdInfo.getUserId());
if (bdUserProfile != null) {
@ -256,11 +273,11 @@ public class TeamBdMemberBillListQryExe {
co.setAgentName(bdUserProfile.getUserNickname());
co.setAgentAvatar(bdUserProfile.getUserAvatar());
}
// 2. 查询该 BD 的所有团队
List<BusinessDevelopmentTeam> bdTeamList =
List<BusinessDevelopmentTeam> bdTeamList =
businessDevelopmentTeamService.listByUserId(bdInfo.getUserId());
if (CollectionUtils.isEmpty(bdTeamList)) {
co.setTeamMemberCount(0);
co.setTeamSalaryAmount(BigDecimal.ZERO);
@ -269,30 +286,28 @@ public class TeamBdMemberBillListQryExe {
co.setEndDate("");
return co;
}
// 3. 设置团队数量 BD 管理的 Agent 数量
co.setTeamMemberCount(bdTeamList.size());
// 4. 账单周期日期
String[] dates = TeamBillCycleUtils.parseBillBelongToDateRange(currentBillBelong);
co.setStartDate(dates[0]);
co.setEndDate(dates[1]);
// 5. 获取团队ID集合
Set<Long> teamIdSet = bdTeamList.stream()
.map(BusinessDevelopmentTeam::getTeamId)
.collect(Collectors.toSet());
// 6. 查询账单数据
Map<Long, TeamBillCycle> billCycleMap = teamBillCycleService.mapByTeamIds2(
teamIdSet, currentBillBelong, TeamBillCycleStatus.UNPAID);
Map<Long, TeamBillCycle> billCycleMap = getBillCycleMap(teamIdSet, currentBillBelong);
Map<String, TeamPolicyManager> regionPolicyMap = teamPolicyManagerService.mapRegionReleaseWithCountry(
billCycleMap.values().stream().map(TeamBillCycle::getRegion).collect(Collectors.toSet()));
// 4. 查询当前周期的账单
Map<Long, List<TeamMemberTarget>> memberBillTarget =
teamMemberTargetService.mapMemberBillTarget(teamIdSet, currentBillBelong);
Map<Long, List<TeamMemberTarget>> memberBillTarget = getMemberBillTargetMap(teamIdSet, currentBillBelong);
BigDecimal totalSalary = BigDecimal.ZERO;
for (BusinessDevelopmentTeam bdTeam : bdTeamList) {
@ -300,27 +315,25 @@ public class TeamBdMemberBillListQryExe {
totalSalary = totalSalary.add(getTeamTotalSalary(billCycleMap, memberBillTarget, regionPolicyMap, teamId));
}
co.setTeamSalaryAmount(totalSalary);
// 8. 获取所有 Agent ID
Set<Long> teamIds = bdTeamList.stream()
.map(BusinessDevelopmentTeam::getTeamId)
.collect(Collectors.toSet());
// 9. 查询充值数据并汇总
Map<Long, BigDecimal> rechargeMap = getRechargeMap(currentBillBelong, teamIds);
BigDecimal totalRecharge = rechargeMap.values().stream()
.reduce(BigDecimal.ZERO, BigDecimal::add);
co.setTeamRechargeAmount(totalRecharge);
return co;
}
/**
* 创建空结果.
*/
private TeamBdMemberCO createEmptyResult() {
Integer currentBillBelong = TeamBillCycleUtils.getCalcBillBelong();
private TeamBdMemberCO createEmptyResult(Integer currentBillBelong) {
TeamBdMemberCO result = new TeamBdMemberCO();
result.setMemberBillList(Collections.emptyList());
result.setMemberCount(0);
@ -329,15 +342,34 @@ public class TeamBdMemberBillListQryExe {
result.setTotalSalary(BigDecimal.ZERO);
result.setTotalRecharge(BigDecimal.ZERO);
result.setBillTitle(TeamBillCycleUtils.parseBillBelongToDateRangeStr(currentBillBelong));
// 空结果也要计算 BD 工资信息全部为0
BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong,
BdHistoryCO bdHistoryCO = calculateCurrentBdBill(currentBillBelong,
0, BigDecimal.ZERO, BigDecimal.ZERO);
result.setBdHistoryCO(bdHistoryCO);
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
private Map<Long, BigDecimal> getRechargeMap(Integer currentBillBelong, Set<Long> teamIds) {
if (CollectionUtils.isEmpty(teamIds)) {
@ -404,7 +436,7 @@ public class TeamBdMemberBillListQryExe {
Integer billBelong) {
TeamBdMemberBillCO co = new TeamBdMemberBillCO();
// 代理用户信息
UserProfileDTO userProfile = userProfileMap.get(bdTeam.getAgentId());
if (userProfile != null) {
@ -416,7 +448,7 @@ public class TeamBdMemberBillListQryExe {
// 团队信息
co.setTeamId(bdTeam.getTeamId());
TeamProfile teamProfile = teamProfileMap.get(bdTeam.getTeamId());
if (teamProfile != null) {
Long memberCount = Optional.ofNullable(teamProfile.getCounter())
@ -449,8 +481,15 @@ public class TeamBdMemberBillListQryExe {
}
List<TeamMemberTarget> targetList = teamMemberTargetMap.get(teamId);
List<TeamBillMemberTarget> memberTargets = TeamBillCycleUtils.calculatorPolicyTargetWithMap(targetList, policyManagerMap);
return ownTotalSalary(memberTargets);
if (CollectionUtils.isNotEmpty(targetList)) {
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) {
@ -470,7 +509,7 @@ public class TeamBdMemberBillListQryExe {
*/
private BdHistoryCO calculateCurrentBdBill(Integer billBelong,
Integer agencyNumber,
BigDecimal teamSalaryAmount,
BigDecimal teamSalaryAmount,
BigDecimal teamRechargeAmount) {
String billTitle = TeamBillCycleUtils.parseBillBelongToDateRangeStr(billBelong);
@ -482,22 +521,19 @@ public class TeamBdMemberBillListQryExe {
currentBill.setTeamRechargeAmount(teamRechargeAmount);
currentBill.setStatus(BdSettlementStatus.PENDING.name());
currentBill.setStatusText("In Progress");
// 创建临时结算记录用于政策匹配
BdSalarySettlementRecord tempRecord = new BdSalarySettlementRecord();
// 获取所有启用的政策
List<BdSalaryPolicy> salaryPolicies = bdSalaryPolicyService.listEnabledByType(BdPolicyType.TEAM_SALARY);
List<BdSalaryPolicy> rechargePolicies = bdSalaryPolicyService.listEnabledByType(BdPolicyType.TEAM_RECHARGE);
// 匹配政策1Team Salary Policy
SalaryCalcResult salaryResult = BdSalarySettlementServiceImpl.matchAndCalculate(
salaryPolicies, agencyNumber, teamSalaryAmount);
currentBill.setBdIncomeSalary(salaryResult.getSalary());
currentBill.setBdRatioSalary(salaryResult.getCommissionRate());
// 匹配政策2Team Recharge Policy
// 匹配政策2Team Recharge Policy
SalaryCalcResult rechargeResult = BdSalarySettlementServiceImpl.matchAndCalculate(
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.model.user.UserProfile;
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.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.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.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.inner.asserts.PropsErrorCode;
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.dto.WalletReceiptDTO;
import com.red.circle.wallet.inner.model.dto.WalletReceiptResDTO;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Timestamp;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@ -73,10 +80,12 @@ public class PropsPurchasingCmdExe {
private final EnumConfigCacheService enumConfigCacheService;
private final RoomProfileManagerService roomProfileManagerService;
private final PropsVipActualEquityService propsVipActualEquityService;
private final TaskMqMessage taskMqMessage;
private final PropsNobleVipClient propsNobleVipClient;
private final UserProfileAppConvertor userProfileAppConvertor;
private final UserRegionGateway userRegionGateway;
private final TaskMqMessage taskMqMessage;
private final PropsNobleVipClient propsNobleVipClient;
private final UserProfileAppConvertor userProfileAppConvertor;
private final UserRegionGateway userRegionGateway;
private final PropsBackpackService propsBackpackService;
private final PropsNobleVipAbilityService propsNobleVipAbilityService;
public BigDecimal execute(PropsPurchasingCmd cmd) {
if (cmd.getDays() <= 0) {
@ -123,10 +132,12 @@ public class PropsPurchasingCmdExe {
if (commodity.getPropsAbility() instanceof NobleVipAbility nobleVipAbility) {
log.warn("查询赠送参数信息:{}"+JSON.toJSON(commodity));
nobleVipAbility = commodity.getNobleVipAbility();
if (nobleVipAbility.cardId() != null) {
propsStoreGateway.shippingProductProps(new ProductProps()
nobleVipAbility = commodity.getNobleVipAbility();
expireSupersededNobleVipMaterials(cmd.acceptUser(), commodity, nobleVipAbility,
TimestampUtils.now());
if (nobleVipAbility.cardId() != null) {
propsStoreGateway.shippingProductProps(new ProductProps()
.setInitiateUserId(cmd.requiredReqUserId())
.setAcceptUserId(cmd.acceptUser())
.setDays(cmd.getDays())
@ -186,11 +197,70 @@ public class PropsPurchasingCmdExe {
propsVipActualEquityService.save(cmd.getAcceptUserId(),
commodity.getPropsResources().getId(), Long.valueOf(cmd.getDays()));
}
}
}
private void sendGiveAwayNotice(PropsPurchasingCmd cmd, PropsStoreCommodity commodity) {
}
}
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) {
UserProfileDTO userProfileDTO = userProfileAppConvertor.toUserProfileDTO(userProfileGateway.getByUserId(cmd.requiredReqUserId()));
RegionConfig region = userRegionGateway.getRegionConfigByUserId(userProfileDTO.getId());

View File

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

View File

@ -1,14 +1,10 @@
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 jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
/**
* BD 成员账单查询命令.
*
@ -22,4 +18,9 @@ public class TeamBdMemberBillCmd extends AppExtCommand {
@NotBlank
private String type;
/**
* 账单月份格式 yyyyMM为空时默认当前月
*/
private Integer billBelong;
}

View File

@ -115,9 +115,9 @@ public class TeamBillCycleServiceImpl implements TeamBillCycleService {
return Maps.newHashMap();
}
Criteria criteria = new Criteria().orOperator(
Criteria.where("billBelong").is(billBelong)
);
Criteria criteria = String.valueOf(billBelong).length() == 6
? getBillBelongCriteria(billBelong)
: new Criteria().orOperator(Criteria.where("billBelong").is(billBelong));
List<TeamBillCycle> teamBillCycles = mongoTemplate.find(
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.TeamProfileService;
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.model.cmd.team.TeamManualSalaryPaymentCmd;
import com.red.circle.other.inner.model.cmd.team.TeamManualSalaryPaymentQryCmd;
@ -85,8 +86,9 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
public PageResult<TeamManualSalaryPaymentDTO> page(TeamManualSalaryPaymentQryCmd cmd) {
validatePage(cmd);
Integer billBelong = resolveBillBelong(cmd.getBillBelong());
List<ManualSalaryRow> rows = buildRows(cmd.getSysOrigin(), cmd.getCountryCode(),
singletonUserId(cmd.getUserId()), TeamBillCycleUtils.getCalcBillBelong());
singletonUserId(cmd.getUserId()), billBelong);
rows = sortRows(rows, cmd.getSortField(), cmd.getSortOrder());
int cursor = 1;
@ -114,7 +116,7 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
public List<TeamManualSalaryPaymentResultDTO> pay(TeamManualSalaryPaymentCmd cmd) {
validatePay(cmd);
Integer billBelong = TeamBillCycleUtils.getCalcBillBelong();
Integer billBelong = resolveBillBelong(cmd.getBillBelong());
String lockKey = MANUAL_SALARY_LOCK_PREFIX + cmd.getSysOrigin() + ":" + cmd.getCountryCode()
+ ":" + billBelong;
ResponseAssert.isTrue(CommonErrorCode.OPERATION_CONFLICT,
@ -130,10 +132,13 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
continue;
}
TeamSalaryPayment payment = teamSalaryPaymentService.createIfAbsent(row.getUserId(),
billBelong, row.getSysOrigin());
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
&& Boolean.TRUE.equals(teamSalaryPaymentService.checkRepeatPayment(payment.getId(),
row.getUserId(), row.getRegion(), policyPayment.getLevel(),
@ -141,10 +146,6 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
continue;
}
Long receiverUserId = policyPayment.isAnchor() ? row.getUserId() : row.getTeamOwnId();
if (Objects.isNull(receiverUserId)) {
continue;
}
BigDecimal issueSalary = policyPayment.getSalary().compareTo(BigDecimal.ZERO) > 0
? policyPayment.getSalary()
: BigDecimal.ZERO.setScale(2, RoundingMode.DOWN);
@ -240,8 +241,14 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
Map<Long, TeamMember> memberMap = teamMemberService.mapByMemberIds(memberIds);
Map<Long, TeamProfile> teamProfileMap = teamProfileService.mapProfileByIds(teamIds);
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
.listByPaymentOrRecipientUserIdsAndDateNumbers(sysOrigin, memberIds,
.listByPaymentOrRecipientUserIdsAndDateNumbers(sysOrigin, paymentRelatedUserIds,
salaryPaymentBillBelongs);
Map<String, TeamSalaryPayment> salaryPaymentMap = toSalaryPaymentMap(salaryPayments);
@ -337,9 +344,13 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
if (Objects.isNull(userId) || CollectionUtils.isEmpty(salaryPaymentMap)) {
return List.of();
}
return getMergedSalaryPaymentBillBelongs(billBelong).stream()
.map(item -> salaryPaymentMap.get(paymentId(userId, item)))
Set<Integer> mergedBillBelongs = getMergedSalaryPaymentBillBelongs(billBelong);
return salaryPaymentMap.values().stream()
.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());
}
@ -368,24 +379,27 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
List<TeamSalaryPayment> cyclePayments = listCyclePayments(salaryPaymentMap,
target.getUserId(), billBelong);
PaymentSummary receivedPaymentSummary = summarizeReceivedPayments(
new ArrayList<>(salaryPaymentMap.values()), target.getUserId());
BigDecimal expectedMemberSalary = salaryValue(billTarget.getMemberSalary());
BigDecimal expectedAgentSalary = salaryValue(billTarget.getOwnSalary());
BigDecimal expectedSalary = expectedTotalSalary(billTarget);
BigDecimal payableSalary = expectedSalary.subtract(receivedPaymentSummary.getTotalIssuedSalary())
BigDecimal expectedAgentSalary = Objects.nonNull(teamProfile.getOwnUserId())
? salaryValue(billTarget.getOwnSalary())
: BigDecimal.ZERO.setScale(2, RoundingMode.DOWN);
BigDecimal expectedSalary = expectedMemberSalary.add(expectedAgentSalary)
.setScale(2, RoundingMode.DOWN);
PaymentSummary receivedPaymentSummary = summarizeTargetEntitlementPayments(cyclePayments,
target.getUserId(), expectedMemberSalary, expectedAgentSalary);
BigDecimal payableMemberSalary = expectedMemberSalary
.subtract(receivedPaymentSummary.getMemberIssuedSalary())
.setScale(2, RoundingMode.DOWN);
BigDecimal payableAgentSalary = expectedAgentSalary
.subtract(receivedPaymentSummary.getAgentIssuedSalary())
.setScale(2, RoundingMode.DOWN);
BigDecimal payableSalary = payableAmount(payableMemberSalary).add(
payableAmount(payableAgentSalary))
.setScale(2, RoundingMode.DOWN);
boolean currentLevelSettled = areCurrentRolesSettled(cyclePayments, target.getUserId(), region,
billTarget, payableSalary);
billTarget, receivedPaymentSummary, expectedMemberSalary, expectedAgentSalary);
List<PolicyPayment> policyPayments = buildSettlementPayments(policyManager, billTarget,
cyclePayments, target.getUserId(), region, receivedPaymentSummary, payableSalary,
currentLevelSettled);
cyclePayments, target.getUserId(), region, receivedPaymentSummary, currentLevelSettled);
TeamManualSalaryPaymentDTO dto = new TeamManualSalaryPaymentDTO()
.setId(target.getId())
@ -396,7 +410,7 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
.setTeamOwnId(teamProfile.getOwnUserId())
.setUserId(target.getUserId())
.setBillBelong(billBelong)
.setLastSettlementTarget(getLastSettlementTarget(cyclePayments))
.setLastSettlementTarget(getLastSettlementTarget(cyclePayments, target.getUserId()))
.setCurrentTarget(target.sumAcceptGiftValue())
.setPolicyLevel(billTarget.getLevel())
.setSatisfyLevel(billTarget.getSatisfyLevel())
@ -413,6 +427,7 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
.setMemberOverIssuedSalary(receivedPaymentSummary.getMemberOverIssuedSalary())
.setAgentOverIssuedSalary(receivedPaymentSummary.getAgentOverIssuedSalary())
.setCurrentSalaryBalance(getAccurateBalance(target.getUserId()))
.setAgentMember(isAgentMember(member, teamProfile, target))
.setPaid(currentLevelSettled);
return new ManualSalaryRow(dto, target, teamProfile, policyManager, billTarget, policyPayments);
@ -468,34 +483,27 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
.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) {
return Optional.ofNullable(value)
.orElse(BigDecimal.ZERO)
.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,
TeamBillMemberTarget billTarget, List<TeamSalaryPayment> cyclePayments, Long userId,
String region, PaymentSummary paymentSummary, BigDecimal payableSalary,
boolean currentLevelSettled) {
String region, PaymentSummary paymentSummary, boolean currentLevelSettled) {
if (currentLevelSettled || Objects.isNull(billTarget)
|| Objects.isNull(billTarget.getLevel())) {
return List.of();
}
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 agentExpected = salaryValue(billTarget.getOwnSalary());
BigDecimal memberMissing = memberExpected.subtract(paymentSummary.getMemberIssuedSalary())
@ -508,31 +516,17 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
billTarget.getLevel(), Boolean.FALSE);
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) {
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);
}
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(),
BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.TRUE));
}
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));
}
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(),
BigDecimal.ZERO.setScale(2, RoundingMode.DOWN), Boolean.FALSE));
}
return payments;
}
@ -549,21 +543,25 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
}
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())
|| payableSalary.compareTo(BigDecimal.ZERO) > 0) {
|| Objects.isNull(paymentSummary)) {
return false;
}
return isRoleSettled(cyclePayments, userId, region, billTarget.getLevel(), Boolean.TRUE,
salaryValue(billTarget.getMemberSalary()))
expectedMemberSalary, paymentSummary.getMemberIssuedSalary())
&& isRoleSettled(cyclePayments, userId, region, billTarget.getLevel(), Boolean.FALSE,
salaryValue(billTarget.getOwnSalary()));
expectedAgentSalary, paymentSummary.getAgentIssuedSalary());
}
private boolean isRoleSettled(List<TeamSalaryPayment> cyclePayments, Long userId, String region,
Integer policyLevel, Boolean anchor, BigDecimal expectedSalary) {
return expectedSalary.compareTo(BigDecimal.ZERO) <= 0
|| hasPaidPolicy(cyclePayments, userId, region, policyLevel, anchor);
Integer policyLevel, Boolean anchor, BigDecimal expectedSalary, BigDecimal issuedSalary) {
if (expectedSalary.compareTo(BigDecimal.ZERO) <= 0) {
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,
@ -575,36 +573,39 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
&& Objects.equals(details.getAnchor(), anchor));
}
private Long getLastSettlementTarget(List<TeamSalaryPayment> cyclePayments) {
private Long getLastSettlementTarget(List<TeamSalaryPayment> cyclePayments, Long targetUserId) {
return listValidSalaryDetails(cyclePayments).stream()
.filter(details -> Objects.equals(details.getTeamMemberId(), targetUserId))
.max(Comparator.comparing(details -> Optional.ofNullable(details.getCreateTime())
.orElse(new Timestamp(0))))
.map(TeamSalaryPaymentDetails::getTarget)
.orElse(0L);
}
private PaymentSummary summarizePayments(List<TeamSalaryPayment> cyclePayments) {
return summarizeDetails(listValidSalaryDetails(cyclePayments));
}
private PaymentSummary summarizeReceivedPayments(List<TeamSalaryPayment> cyclePayments,
Long receiverUserId) {
if (Objects.isNull(receiverUserId)) {
private PaymentSummary summarizeTargetEntitlementPayments(List<TeamSalaryPayment> cyclePayments,
Long targetUserId, BigDecimal expectedMemberSalary, BigDecimal expectedAgentSalary) {
if (Objects.isNull(targetUserId)) {
return emptyPaymentSummary();
}
List<TeamSalaryPaymentDetails> details = listValidSalaryDetails(cyclePayments).stream()
.filter(detail -> Objects.equals(receiverUserId, getSalaryReceiverUserId(detail)))
.filter(detail -> Objects.equals(targetUserId, detail.getTeamMemberId()))
.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 agentIssuedSalary = sumDetailsSalary(details, Boolean.FALSE);
BigDecimal totalIssuedSalary = memberIssuedSalary.add(agentIssuedSalary)
.setScale(2, RoundingMode.DOWN);
BigDecimal memberOverIssuedSalary = getOverIssuedSalary(details, Boolean.TRUE);
BigDecimal agentOverIssuedSalary = getOverIssuedSalary(details, Boolean.FALSE);
BigDecimal memberOverIssuedSalary = memberIssuedSalary.subtract(
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)
.setScale(2, RoundingMode.DOWN);
return new PaymentSummary(totalIssuedSalary, memberIssuedSalary, agentIssuedSalary,
@ -616,17 +617,6 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
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) {
return details.stream()
.filter(detail -> Objects.equals(detail.getAnchor(), anchor))
@ -635,38 +625,6 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
.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(
List<TeamSalaryPayment> cyclePayments) {
if (CollectionUtils.isEmpty(cyclePayments)) {
@ -729,6 +687,25 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
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) {
ResponseAssert.notNull(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd);
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getSysOrigin());
@ -741,10 +718,6 @@ public class TeamManualSalaryPaymentClientServiceImpl implements
ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getCountryCode());
}
private String paymentId(Long userId, Integer billBelong) {
return userId + "_" + billBelong;
}
private static class PaymentSummary {
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.TeamSalaryPaymentService;
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.model.cmd.team.TeamManualSalaryPaymentCmd;
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.List;
import java.util.Map;
import java.util.Objects;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.test.util.ReflectionTestUtils;
@ -73,13 +75,16 @@ class TeamManualSalaryPaymentClientServiceImplTest {
List<?> payments = ReflectionTestUtils.invokeMethod(service, "buildSettlementPayments",
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);
assertEquals(6, ReflectionTestUtils.getField(payment, "level"));
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
@ -101,7 +106,7 @@ class TeamManualSalaryPaymentClientServiceImplTest {
List<?> payments = ReflectionTestUtils.invokeMethod(service, "buildSettlementPayments",
null, billTarget, List.of(), userId, region, paymentSummary,
new BigDecimal("-1.25"), false);
false);
assertEquals(2, payments.size());
assertEquals(BigDecimal.ZERO.setScale(2), ReflectionTestUtils.getField(payments.get(0),
@ -193,7 +198,8 @@ class TeamManualSalaryPaymentClientServiceImplTest {
.thenReturn(new TeamSalaryPayment().setId(paymentId));
when(userBankBalanceClient.getBalance(userId))
.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(userBankRunningWaterClient.add(any())).thenReturn(ResultResponse.success());
when(teamSalaryPaymentService.mapPolicy(paymentId)).thenReturn(new HashMap<>());
@ -205,16 +211,212 @@ class TeamManualSalaryPaymentClientServiceImplTest {
assertEquals(1, results.size());
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.forClass(TeamSalaryPaymentDetails.class);
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());
verify(teamSalaryPaymentService, never()).checkRepeatPayment(anyString(), anyLong(),
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,
BigDecimal agentIssuedSalary) throws Exception {
Class<?> summaryClass = Class.forName(