diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamManualSalaryPaymentCmd.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamManualSalaryPaymentCmd.java index 20ecf358..a83fb1c3 100644 --- a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamManualSalaryPaymentCmd.java +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamManualSalaryPaymentCmd.java @@ -28,6 +28,11 @@ public class TeamManualSalaryPaymentCmd extends Command { */ private String countryCode; + /** + * 工资账期,为空时使用当前整月账期. + */ + private Integer billBelong; + /** * 指定发放用户,为空时按国家全量发放. */ diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamManualSalaryPaymentQryCmd.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamManualSalaryPaymentQryCmd.java index e8cca771..f226cc01 100644 --- a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamManualSalaryPaymentQryCmd.java +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/cmd/team/TeamManualSalaryPaymentQryCmd.java @@ -27,6 +27,11 @@ public class TeamManualSalaryPaymentQryCmd extends PageCommand { */ private String countryCode; + /** + * 工资账期,为空时使用当前整月账期. + */ + private Integer billBelong; + /** * 用户 id. */ diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/agency/agency/TeamManualSalaryPaymentDTO.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/agency/agency/TeamManualSalaryPaymentDTO.java index d25592d8..1eb12dc1 100644 --- a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/agency/agency/TeamManualSalaryPaymentDTO.java +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/model/dto/agency/agency/TeamManualSalaryPaymentDTO.java @@ -146,6 +146,11 @@ public class TeamManualSalaryPaymentDTO implements Serializable { */ private BigDecimal currentSalaryBalance; + /** + * 当前用户是否为团队代理本人. + */ + private Boolean agentMember; + /** * 当前档位是否已发放. */ diff --git a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/team/TeamSalaryBackServiceImpl.java b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/team/TeamSalaryBackServiceImpl.java index 51a9ba4c..10bce408 100644 --- a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/team/TeamSalaryBackServiceImpl.java +++ b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/app/team/TeamSalaryBackServiceImpl.java @@ -228,6 +228,7 @@ public class TeamSalaryBackServiceImpl implements TeamSalaryBackService { .setMemberOverIssuedSalary(dto.getMemberOverIssuedSalary()) .setAgentOverIssuedSalary(dto.getAgentOverIssuedSalary()) .setCurrentSalaryBalance(dto.getCurrentSalaryBalance()) + .setAgentMember(dto.getAgentMember()) .setPaid(dto.getPaid()); } diff --git a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/gm/GmVipGiftServiceImpl.java b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/gm/GmVipGiftServiceImpl.java index 50012920..38fea6e4 100644 --- a/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/gm/GmVipGiftServiceImpl.java +++ b/rc-service/rc-service-console/console-application/src/main/java/com/red/circle/console/app/service/gm/GmVipGiftServiceImpl.java @@ -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 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 previousResourceIds, Timestamp now) { + Set targetResourceIds = vipResourceIds(targetConfig); + Set 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 vipResourceIds(VipLevelConfig config) { + Set 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 vipResourceIds(UserVipState state) { + Set 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 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) { diff --git a/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clienobject/team/TeamManualSalaryCO.java b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clienobject/team/TeamManualSalaryCO.java index 46b8ed1c..34267332 100644 --- a/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clienobject/team/TeamManualSalaryCO.java +++ b/rc-service/rc-service-console/console-client/src/main/java/com/red/circle/console/app/dto/clienobject/team/TeamManualSalaryCO.java @@ -52,5 +52,6 @@ public class TeamManualSalaryCO implements Serializable { private BigDecimal memberOverIssuedSalary; private BigDecimal agentOverIssuedSalary; private BigDecimal currentSalaryBalance; + private Boolean agentMember; private Boolean paid; } diff --git a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/TestRestController.java b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/TestRestController.java index 12d8482f..f8dabab0 100644 --- a/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/TestRestController.java +++ b/rc-service/rc-service-other/other-adapter/src/main/java/com/red/circle/other/adapter/app/TestRestController.java @@ -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) { diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/bd/query/TeamBdMemberBillListQryExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/bd/query/TeamBdMemberBillListQryExe.java index dbae1d47..c939d92b 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/bd/query/TeamBdMemberBillListQryExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/bd/query/TeamBdMemberBillListQryExe.java @@ -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 bdTeamList = businessDevelopmentTeamService.listByUserId(bdUserId); - + if (CollectionUtils.isEmpty(bdTeamList)) { - return createEmptyResult(); + return createEmptyResult(currentBillBelong); } - // 2. 获取当前账单周期 - Integer currentBillBelong = TeamBillCycleUtils.getCalcBillBelong(); - // 3. 获取团队ID集合 Set teamIdSet = bdTeamList.stream() .map(BusinessDevelopmentTeam::getTeamId) .collect(Collectors.toSet()); // 获取所有区域的政策配置 - Map billCycleMap = teamBillCycleService.mapByTeamIds2( - teamIdSet, currentBillBelong, TeamBillCycleStatus.UNPAID); + Map billCycleMap = getBillCycleMap(teamIdSet, currentBillBelong); Map regionPolicyMap = teamPolicyManagerService.mapRegionReleaseWithCountry( billCycleMap.values().stream().map(TeamBillCycle::getRegion).collect(Collectors.toSet())); // 4. 查询当前周期的账单 - Map> memberBillTarget = - teamMemberTargetService.mapMemberBillTarget(teamIdSet, currentBillBelong); + Map> memberBillTarget = getMemberBillTargetMap(teamIdSet, currentBillBelong); // 5. 获取团队Profile信息 Map 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 bdList = + List bdList = businessDevelopmentBaseInfoService.listBdByLeadUserId(bdLeaderUserId); - + if (CollectionUtils.isEmpty(bdList)) { - return createEmptyResult(); + return createEmptyResult(currentBillBelong); } - // 2. 获取当前账单周期 - Integer currentBillBelong = TeamBillCycleUtils.getCalcBillBelong(); - // 3. 收集所有 BD 的用户ID Set bdUserIdSet = bdList.stream() .map(BusinessDevelopmentBaseInfo::getUserId) .collect(Collectors.toSet()); - + // 4. 批量查询所有 BD 的用户信息 Map bdUserProfileMap = userProfileAppConvertor.toMapUserProfileDTO( userProfileGateway.mapByUserIds(bdUserIdSet)); - + // 5. 为每个 BD 构建汇总数据 List 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 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 bdTeamList = + List 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 teamIdSet = bdTeamList.stream() .map(BusinessDevelopmentTeam::getTeamId) .collect(Collectors.toSet()); - + // 6. 查询账单数据 - Map billCycleMap = teamBillCycleService.mapByTeamIds2( - teamIdSet, currentBillBelong, TeamBillCycleStatus.UNPAID); + Map billCycleMap = getBillCycleMap(teamIdSet, currentBillBelong); Map regionPolicyMap = teamPolicyManagerService.mapRegionReleaseWithCountry( billCycleMap.values().stream().map(TeamBillCycle::getRegion).collect(Collectors.toSet())); // 4. 查询当前周期的账单 - Map> memberBillTarget = - teamMemberTargetService.mapMemberBillTarget(teamIdSet, currentBillBelong); + Map> 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 teamIds = bdTeamList.stream() .map(BusinessDevelopmentTeam::getTeamId) .collect(Collectors.toSet()); - + // 9. 查询充值数据并汇总 Map 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 getBillCycleMap(Set teamIdSet, Integer billBelong) { + Map billCycleMap = new HashMap<>( + teamBillCycleService.mapByTeamIds2(teamIdSet, billBelong, TeamBillCycleStatus.SETTLED)); + billCycleMap.putAll(teamBillCycleService.mapByTeamIds2(teamIdSet, billBelong, TeamBillCycleStatus.UNPAID)); + return billCycleMap; + } + + private Map> getMemberBillTargetMap(Set teamIdSet, Integer billBelong) { + if (CollectionUtils.isEmpty(teamIdSet)) { + return Collections.emptyMap(); + } + if (String.valueOf(billBelong).length() == 6) { + Map> result = new HashMap<>(); + teamIdSet.forEach(teamId -> result.put(teamId, teamMemberTargetService.listByMonthlyBillBelong(teamId, billBelong))); + return result; + } + return teamMemberTargetService.mapMemberBillTarget(teamIdSet, billBelong); + } + @NotNull private Map getRechargeMap(Integer currentBillBelong, Set 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 targetList = teamMemberTargetMap.get(teamId); - List memberTargets = TeamBillCycleUtils.calculatorPolicyTargetWithMap(targetList, policyManagerMap); - return ownTotalSalary(memberTargets); + if (CollectionUtils.isNotEmpty(targetList)) { + List 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 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 salaryPolicies = bdSalaryPolicyService.listEnabledByType(BdPolicyType.TEAM_SALARY); List rechargePolicies = bdSalaryPolicyService.listEnabledByType(BdPolicyType.TEAM_RECHARGE); - + // 匹配政策1:Team Salary Policy SalaryCalcResult salaryResult = BdSalarySettlementServiceImpl.matchAndCalculate( salaryPolicies, agencyNumber, teamSalaryAmount); currentBill.setBdIncomeSalary(salaryResult.getSalary()); currentBill.setBdRatioSalary(salaryResult.getCommissionRate()); - - // 匹配政策2:Team Recharge Policy + + // 匹配政策2:Team Recharge Policy SalaryCalcResult rechargeResult = BdSalarySettlementServiceImpl.matchAndCalculate( rechargePolicies, agencyNumber, teamRechargeAmount); diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/material/PropsPurchasingCmdExe.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/material/PropsPurchasingCmdExe.java index 6fc53124..f68642be 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/material/PropsPurchasingCmdExe.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/command/material/PropsPurchasingCmdExe.java @@ -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 targetIds = nobleVipMaterialIds(targetAbility); + if (Objects.nonNull(commodity.getPropsResources()) + && Objects.nonNull(commodity.getPropsResources().getId())) { + targetIds.add(commodity.getPropsResources().getId()); + } + + Set 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 nobleVipMaterialIds(NobleVipAbility ability) { + Set 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 nobleVipMaterialIds(PropsNobleVipAbility ability) { + Set 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 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()); diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/TeamBillTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/TeamBillTask.java index 21500339..0d56815f 100644 --- a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/TeamBillTask.java +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/TeamBillTask.java @@ -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(); diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/TeamBdMemberBillCmd.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/TeamBdMemberBillCmd.java index c050d9f8..a2b7cc86 100644 --- a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/TeamBdMemberBillCmd.java +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/cmd/TeamBdMemberBillCmd.java @@ -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; + } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamBillCycleServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamBillCycleServiceImpl.java index 36ec8055..9c66ad28 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamBillCycleServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamBillCycleServiceImpl.java @@ -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 teamBillCycles = mongoTemplate.find( Query.query(criteria.and("teamId").in(teamIds).and("status").is(status)), diff --git a/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImpl.java b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImpl.java index f756861d..9ac0b601 100644 --- a/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImpl.java +++ b/rc-service/rc-service-other/other-inner-endpoint/src/main/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImpl.java @@ -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 page(TeamManualSalaryPaymentQryCmd cmd) { validatePage(cmd); + Integer billBelong = resolveBillBelong(cmd.getBillBelong()); List 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 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 memberMap = teamMemberService.mapByMemberIds(memberIds); Map teamProfileMap = teamProfileService.mapProfileByIds(teamIds); Set salaryPaymentBillBelongs = getMergedSalaryPaymentBillBelongs(billBelong); + Set paymentRelatedUserIds = new HashSet<>(memberIds); + teamProfileMap.values().stream() + .filter(Objects::nonNull) + .map(TeamProfile::getOwnUserId) + .filter(Objects::nonNull) + .forEach(paymentRelatedUserIds::add); List salaryPayments = teamSalaryPaymentService - .listByPaymentOrRecipientUserIdsAndDateNumbers(sysOrigin, memberIds, + .listByPaymentOrRecipientUserIdsAndDateNumbers(sysOrigin, paymentRelatedUserIds, salaryPaymentBillBelongs); Map 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 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 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 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 buildSettlementPayments(TeamPolicyManager policyManager, TeamBillMemberTarget billTarget, List 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 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 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 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 cyclePayments, Long userId, String region, @@ -575,36 +573,39 @@ public class TeamManualSalaryPaymentClientServiceImpl implements && Objects.equals(details.getAnchor(), anchor)); } - private Long getLastSettlementTarget(List cyclePayments) { + private Long getLastSettlementTarget(List 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 cyclePayments) { - return summarizeDetails(listValidSalaryDetails(cyclePayments)); - } - - private PaymentSummary summarizeReceivedPayments(List cyclePayments, - Long receiverUserId) { - if (Objects.isNull(receiverUserId)) { + private PaymentSummary summarizeTargetEntitlementPayments(List cyclePayments, + Long targetUserId, BigDecimal expectedMemberSalary, BigDecimal expectedAgentSalary) { + if (Objects.isNull(targetUserId)) { return emptyPaymentSummary(); } List 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 details) { + private PaymentSummary summarizeDetails(List 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 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 details) { - return getOverIssuedSalary(details, null); - } - - private BigDecimal getOverIssuedSalary(List details, Boolean anchor) { - if (CollectionUtils.isEmpty(details)) { - return BigDecimal.ZERO.setScale(2, RoundingMode.DOWN); - } - Set 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 listValidSalaryDetails( List 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; diff --git a/rc-service/rc-service-other/other-inner-endpoint/src/test/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImplTest.java b/rc-service/rc-service-other/other-inner-endpoint/src/test/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImplTest.java index 4bd8ef87..41f4d86a 100644 --- a/rc-service/rc-service-other/other-inner-endpoint/src/test/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImplTest.java +++ b/rc-service/rc-service-other/other-inner-endpoint/src/test/java/com/red/circle/other/app/inner/service/team/impl/TeamManualSalaryPaymentClientServiceImplTest.java @@ -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 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 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 anchorDetailsCaptor = + ArgumentCaptor.forClass(TeamSalaryPaymentDetails.class); + verify(teamSalaryPaymentService).paymentSalary(eq(anchorPaymentId), + anchorDetailsCaptor.capture()); + assertEquals(anchorUserId, anchorDetailsCaptor.getValue().getTeamMemberId()); + assertEquals(Boolean.TRUE, anchorDetailsCaptor.getValue().getAnchor()); + + ArgumentCaptor 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 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(