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 40773025..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,16 +28,16 @@ public class TeamManualSalaryPaymentCmd extends Command { */ private String countryCode; + /** + * 工资账期,为空时使用当前整月账期. + */ + private Integer billBelong; + /** * 指定发放用户,为空时按国家全量发放. */ private List userIds; - /** - * 月度账期,格式 yyyyMM. 为空时使用当前月. - */ - 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 f2c9056e..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,16 +27,16 @@ public class TeamManualSalaryPaymentQryCmd extends PageCommand { */ private String countryCode; + /** + * 工资账期,为空时使用当前整月账期. + */ + private Integer billBelong; + /** * 用户 id. */ private Long userId; - /** - * 月度账期,格式 yyyyMM. 为空时使用当前月. - */ - private Integer billBelong; - /** * 排序字段. */ 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 06e23df6..b4d32690 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 @@ -229,6 +229,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-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 a6be3d10..13fbbe2c 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 @@ -70,7 +70,6 @@ public class TeamBillTask { consumeProcessPayOutBill(); log.warn("结束账期切换"); } - public void processTeamBillRetry() { if (BILL_CYCLE_SWITCH_PAUSED) { log.warn("process_team_bill_retry skipped: bill cycle switch is temporarily paused"); 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 abc365ca..62dad234 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 @@ -132,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(), @@ -143,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); @@ -242,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); @@ -339,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()); } @@ -367,30 +376,31 @@ public class TeamManualSalaryPaymentClientServiceImpl implements TeamPolicyManager policyManager = getPolicyManager(target.getSysOrigin(), region, countryCode, policyCache); TeamBillMemberTarget billTarget = calculateBillTarget(target, policyManager); - boolean includeAgentSalary = isAgentMember(member, teamProfile, target.getUserId()); + boolean agentMember = isAgentMember(member, teamProfile, target.getUserId()); List cyclePayments = listCyclePayments(salaryPaymentMap, target.getUserId(), billBelong); - PaymentSummary receivedPaymentSummary = summarizeReceivedPayments( - new ArrayList<>(salaryPaymentMap.values()), target.getUserId(), includeAgentSalary); BigDecimal expectedMemberSalary = salaryValue(billTarget.getMemberSalary()); - BigDecimal expectedAgentSalary = includeAgentSalary ? salaryValue(billTarget.getOwnSalary()) + BigDecimal expectedAgentSalary = Objects.nonNull(teamProfile.getOwnUserId()) + ? salaryValue(billTarget.getOwnSalary()) : BigDecimal.ZERO.setScale(2, RoundingMode.DOWN); BigDecimal expectedSalary = expectedMemberSalary.add(expectedAgentSalary) .setScale(2, RoundingMode.DOWN); - BigDecimal payableSalary = expectedSalary.subtract(receivedPaymentSummary.getTotalIssuedSalary()) - .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, expectedMemberSalary, expectedAgentSalary, payableSalary); + billTarget, receivedPaymentSummary, expectedMemberSalary, expectedAgentSalary); List policyPayments = buildSettlementPayments(policyManager, billTarget, - cyclePayments, target.getUserId(), region, receivedPaymentSummary, expectedMemberSalary, - expectedAgentSalary, payableSalary, currentLevelSettled); + cyclePayments, target.getUserId(), region, receivedPaymentSummary, currentLevelSettled); TeamManualSalaryPaymentDTO dto = new TeamManualSalaryPaymentDTO() .setId(target.getId()) @@ -401,11 +411,11 @@ 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()) - .setAgentMember(includeAgentSalary) + .setAgentMember(agentMember) .setExpectedSalary(expectedSalary) .setExpectedMemberSalary(expectedMemberSalary) .setExpectedAgentSalary(expectedAgentSalary) @@ -493,19 +503,23 @@ public class TeamManualSalaryPaymentClientServiceImpl implements .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 memberExpected, - BigDecimal agentExpected, 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()) .setScale(2, RoundingMode.DOWN); BigDecimal agentMissing = agentExpected.subtract(paymentSummary.getAgentIssuedSalary()) @@ -516,31 +530,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; } @@ -557,22 +557,25 @@ public class TeamManualSalaryPaymentClientServiceImpl implements } private boolean areCurrentRolesSettled(List cyclePayments, Long userId, - String region, TeamBillMemberTarget billTarget, BigDecimal expectedMemberSalary, - BigDecimal expectedAgentSalary, 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, - expectedMemberSalary) + expectedMemberSalary, paymentSummary.getMemberIssuedSalary()) && isRoleSettled(cyclePayments, userId, region, billTarget.getLevel(), Boolean.FALSE, - expectedAgentSalary); + 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, @@ -584,33 +587,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 summarizeReceivedPayments(List cyclePayments, - Long receiverUserId, boolean includeAgentSalary) { - 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 -> includeAgentSalary || Boolean.TRUE.equals(detail.getAnchor())) + .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, @@ -622,17 +631,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)) @@ -641,38 +639,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)) { @@ -735,6 +701,13 @@ public class TeamManualSalaryPaymentClientServiceImpl implements teamSalaryPaymentService.updatePolicyMap(paymentId, policyMap); } + private Long getPaymentReceiverUserId(ManualSalaryRow row, PolicyPayment payment) { + if (Objects.isNull(row) || Objects.isNull(payment)) { + return null; + } + return payment.isAnchor() ? row.getUserId() : row.getTeamOwnId(); + } + private void validatePage(TeamManualSalaryPaymentQryCmd cmd) { ResponseAssert.notNull(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd); ResponseAssert.notBlank(ResponseErrorCode.REQUEST_PARAMETER_ERROR, cmd.getSysOrigin()); @@ -757,10 +730,6 @@ public class TeamManualSalaryPaymentClientServiceImpl implements return billBelong; } - 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 8d8fcfcd..31f3c3a6 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 @@ -1,6 +1,7 @@ package com.red.circle.other.app.inner.service.team.impl; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anySet; @@ -27,8 +28,8 @@ import com.red.circle.other.infra.database.mongo.service.team.team.TeamMemberTar import com.red.circle.other.infra.database.mongo.service.team.team.TeamPolicyManagerService; import com.red.circle.other.infra.database.mongo.service.team.team.TeamProfileService; import com.red.circle.other.infra.database.mongo.service.team.team.TeamSalaryPaymentService; -import com.red.circle.other.inner.enums.team.TeamMemberTargetIndex; import com.red.circle.other.inner.enums.team.TeamMemberRole; +import com.red.circle.other.inner.enums.team.TeamMemberTargetIndex; import com.red.circle.other.inner.model.cmd.team.TeamManualSalaryPaymentCmd; import com.red.circle.other.inner.model.dto.agency.agency.TeamManualSalaryPaymentResultDTO; import com.red.circle.wallet.inner.endpoint.bank.UserBankBalanceClient; @@ -39,6 +40,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,14 +75,16 @@ class TeamManualSalaryPaymentClientServiceImplTest { new BigDecimal("9.50")); List payments = ReflectionTestUtils.invokeMethod(service, "buildSettlementPayments", - null, billTarget, List.of(currentPayment), userId, region, paymentSummary, - new BigDecimal("35.00"), new BigDecimal("8.75"), new BigDecimal("12.25"), false); + null, billTarget, List.of(currentPayment), userId, region, paymentSummary, 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,8 +105,7 @@ class TeamManualSalaryPaymentClientServiceImplTest { new BigDecimal("9.00")); List payments = ReflectionTestUtils.invokeMethod(service, "buildSettlementPayments", - null, billTarget, List.of(), userId, region, paymentSummary, - new BigDecimal("35.00"), new BigDecimal("8.75"), new BigDecimal("-1.25"), false); + null, billTarget, List.of(), userId, region, paymentSummary, false); assertEquals(2, payments.size()); assertEquals(BigDecimal.ZERO.setScale(2), ReflectionTestUtils.getField(payments.get(0), @@ -111,6 +114,16 @@ class TeamManualSalaryPaymentClientServiceImplTest { "salary")); } + @Test + void resolveBillBelongShouldRejectInvalidMonth() { + TeamManualSalaryPaymentClientServiceImpl service = + new TeamManualSalaryPaymentClientServiceImpl( + null, null, null, null, null, null, null, null); + + assertThrows(RuntimeException.class, + () -> ReflectionTestUtils.invokeMethod(service, "resolveBillBelong", 202613)); + } + @Test void payShouldReturnTopUpDetailsWhenCurrentLevelRecordExistsButSalaryIsInsufficient() { RedisService redisService = mock(RedisService.class); @@ -194,7 +207,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<>()); @@ -207,18 +221,18 @@ 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 payShouldNotRepeatAgentSalaryAlreadyPaidToTeamOwner() { + void payShouldIssueMemberSalaryToAnchorAndAgentSalaryToTeamOwner() { RedisService redisService = mock(RedisService.class); TeamMemberService teamMemberService = mock(TeamMemberService.class); TeamProfileService teamProfileService = mock(TeamProfileService.class); @@ -235,85 +249,186 @@ class TeamManualSalaryPaymentClientServiceImplTest { String sysOrigin = "LIKEI"; String countryCode = "PH"; String region = "2046067717605224449"; - Long userId = 2052023053021409281L; - Long ownerUserId = 2051169779338768386L; - Long teamId = 2051672510944047106L; + Long anchorUserId = 2054591808909930498L; + Long ownerUserId = 2057118411766427649L; + Long teamId = 2054614353419759617L; Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); - String paymentId = userId + "_" + billBelong; + String anchorPaymentId = anchorUserId + "_" + billBelong; + String ownerPaymentId = ownerUserId + "_" + billBelong; TeamMemberTarget target = new TeamMemberTarget() .setId("target-id") - .setTimeId(1779030072251003137L) + .setTimeId(2056024761217118210L) .setSysOrigin(sysOrigin) .setRegion(region) .setCountryCode(countryCode) .setTeamId(teamId) - .setUserId(userId) + .setUserId(anchorUserId) .setBillBelong(billBelong) .setDailyTargets(List.of(new TeamMemberTargetIndex() - .setOwnOnlineTime(4586L) - .setAcceptGiftValue(2104843L))); - TeamSalaryPayment existingPayment = new TeamSalaryPayment() - .setId(paymentId) - .setUserId(userId) - .setDateNumber(billBelong) - .setSalarySendDetails(List.of( - new TeamSalaryPaymentDetails() - .setTeamMemberId(userId) - .setTeamOwnId(ownerUserId) - .setTeamRegionId(region) - .setPolicyLevel(4) - .setAnchor(Boolean.TRUE) - .setSalary(new BigDecimal("8.00")) - .setRecycled(Boolean.FALSE), - new TeamSalaryPaymentDetails() - .setTeamMemberId(userId) - .setTeamOwnId(ownerUserId) - .setTeamRegionId(region) - .setPolicyLevel(4) - .setAnchor(Boolean.FALSE) - .setSalary(new BigDecimal("2.00")) - .setRecycled(Boolean.FALSE))); + .setOwnOnlineTime(717L) + .setAcceptGiftValue(15151819L))); TeamPolicyManager policyManager = new TeamPolicyManager() .setSysOrigin(sysOrigin) .setRegion(region) .setCountryCode(countryCode) .setPolicy(List.of(new TeamPolicy() - .setLevel(4) - .setTarget(2000000L) + .setLevel(6) + .setTarget(8810000L) .setOnlineTime(10L) .setEffectiveDay(0) - .setMemberSalary(new BigDecimal("8.00")) - .setOwnSalary(new BigDecimal("2.00")) - .setTotalSalary(new BigDecimal("10.00")))); + .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(userId, - new TeamMember().setMemberId(userId).setTeamId(teamId))); + 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(existingPayment)); + anySet(), anySet())).thenReturn(List.of()); when(teamPolicyManagerService.getReleaseByRegionAndType(sysOrigin, region, null, countryCode)) .thenReturn(policyManager); - when(userBankBalanceClient.getBalance(userId)) + 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(userId))); + .setUserIds(List.of(anchorUserId))); assertEquals(0, results.size()); verify(teamSalaryPaymentService, never()).createIfAbsent(anyLong(), any(), anyString()); - verify(userBankBalanceClient, never()).incr(any()); - verify(userBankRunningWaterClient, never()).add(any()); verify(teamSalaryPaymentService, never()).paymentSalary(anyString(), any()); } @Test - void payShouldOnlyIssueMemberSalaryForNormalAnchor() { + void payShouldNotRepeatTargetEntitlementsAlreadyPaidInTargetPaymentDoc() { RedisService redisService = mock(RedisService.class); TeamMemberService teamMemberService = mock(TeamMemberService.class); TeamProfileService teamProfileService = mock(TeamProfileService.class); @@ -337,41 +452,54 @@ class TeamManualSalaryPaymentClientServiceImplTest { String paymentId = userId + "_" + billBelong; TeamMemberTarget target = manualSalaryTarget(sysOrigin, countryCode, region, userId, teamId, billBelong); + TeamSalaryPayment existingPayment = new TeamSalaryPayment() + .setId(paymentId) + .setUserId(userId) + .setDateNumber(billBelong) + .setSalarySendDetails(List.of( + new TeamSalaryPaymentDetails() + .setTeamMemberId(userId) + .setTeamOwnId(ownerUserId) + .setTeamRegionId(region) + .setPolicyLevel(4) + .setAnchor(Boolean.TRUE) + .setSalary(new BigDecimal("8.00")) + .setRecycled(Boolean.FALSE), + new TeamSalaryPaymentDetails() + .setTeamMemberId(userId) + .setTeamOwnId(ownerUserId) + .setTeamRegionId(region) + .setPolicyLevel(4) + .setAnchor(Boolean.FALSE) + .setSalary(new BigDecimal("2.00")) + .setRecycled(Boolean.FALSE))); TeamPolicyManager policyManager = manualSalaryPolicy(sysOrigin, countryCode, region); 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(userId, - new TeamMember().setMemberId(userId).setTeamId(teamId).setRole(TeamMemberRole.MEMBER))); + new TeamMember().setMemberId(userId).setTeamId(teamId) + .setRole(TeamMemberRole.MEMBER))); when(teamProfileService.mapProfileByIds(anySet())).thenReturn(Map.of(teamId, new TeamProfile().setId(teamId).setOwnUserId(ownerUserId).setRegion(region))); when(teamSalaryPaymentService.listByPaymentOrRecipientUserIdsAndDateNumbers(eq(sysOrigin), - anySet(), anySet())).thenReturn(List.of()); + anySet(), anySet())).thenReturn(List.of(existingPayment)); when(teamPolicyManagerService.getReleaseByRegionAndType(sysOrigin, region, null, countryCode)) .thenReturn(policyManager); - when(teamSalaryPaymentService.createIfAbsent(userId, billBelong, sysOrigin)) - .thenReturn(new TeamSalaryPayment().setId(paymentId)); when(userBankBalanceClient.getBalance(userId)) - .thenReturn(ResultResponse.success(BankBalanceDTO.of(0L))) - .thenReturn(ResultResponse.success(BankBalanceDTO.of(800L))); - when(userBankBalanceClient.incr(any())).thenReturn(ResultResponse.success(Boolean.TRUE)); - when(userBankRunningWaterClient.add(any())).thenReturn(ResultResponse.success()); - when(teamSalaryPaymentService.mapPolicy(paymentId)).thenReturn(new HashMap<>()); + .thenReturn(ResultResponse.success(BankBalanceDTO.of(0L))); List results = service.pay(new TeamManualSalaryPaymentCmd() .setSysOrigin(sysOrigin) .setCountryCode(countryCode) .setUserIds(List.of(userId))); - assertEquals(1, results.size()); - assertEquals(userId, results.get(0).getUserId()); - assertEquals(new BigDecimal("8.00"), results.get(0).getIssuedSalary()); - ArgumentCaptor detailsCaptor = - ArgumentCaptor.forClass(TeamSalaryPaymentDetails.class); - verify(teamSalaryPaymentService).paymentSalary(eq(paymentId), detailsCaptor.capture()); - assertEquals(Boolean.TRUE, detailsCaptor.getValue().getAnchor()); - assertEquals(new BigDecimal("8.00"), detailsCaptor.getValue().getSalary()); + assertEquals(0, results.size()); + verify(teamSalaryPaymentService, never()).createIfAbsent(anyLong(), any(), anyString()); + verify(userBankBalanceClient, never()).incr(any()); + verify(userBankRunningWaterClient, never()).add(any()); + verify(teamSalaryPaymentService, never()).paymentSalary(anyString(), any()); } @Test @@ -414,6 +542,7 @@ class TeamManualSalaryPaymentClientServiceImplTest { when(teamSalaryPaymentService.createIfAbsent(userId, billBelong, sysOrigin)) .thenReturn(new TeamSalaryPayment().setId(paymentId)); when(userBankBalanceClient.getBalance(userId)) + .thenReturn(ResultResponse.success(BankBalanceDTO.of(0L))) .thenReturn(ResultResponse.success(BankBalanceDTO.of(0L))) .thenReturn(ResultResponse.success(BankBalanceDTO.of(800L))) .thenReturn(ResultResponse.success(BankBalanceDTO.of(800L)))