diff --git a/rc-service/rc-inner-api/order-inner/order-inner-api/src/main/java/com/red/circle/order/inner/endpoint/api/InAppPurchaseDetailsClientApi.java b/rc-service/rc-inner-api/order-inner/order-inner-api/src/main/java/com/red/circle/order/inner/endpoint/api/InAppPurchaseDetailsClientApi.java index 52c9703f..ff7473c2 100644 --- a/rc-service/rc-inner-api/order-inner/order-inner-api/src/main/java/com/red/circle/order/inner/endpoint/api/InAppPurchaseDetailsClientApi.java +++ b/rc-service/rc-inner-api/order-inner/order-inner-api/src/main/java/com/red/circle/order/inner/endpoint/api/InAppPurchaseDetailsClientApi.java @@ -2,6 +2,7 @@ package com.red.circle.order.inner.endpoint.api; import com.red.circle.framework.dto.ResultResponse; import com.red.circle.order.inner.model.cmd.inapp.InAppPurchaseDetailsQryCmd; +import com.red.circle.order.inner.model.dto.inapp.BatchUserRechargeStatisticsDTO; import com.red.circle.order.inner.model.dto.inapp.CountUserInAppPurchaseDTO; import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO; import java.time.LocalDateTime; @@ -51,4 +52,21 @@ public interface InAppPurchaseDetailsClientApi { @PostMapping("/statistics") ResultResponse> statistics( @RequestBody @Validated InAppPurchaseDetailsQryCmd qryCmd); + + /** + * 批量查询用户充值统计. + * 统计指定用户列表在指定时间范围内的支付成功订单金额总和. + * + * @param userIds 用户ID列表 + * @param sysOrigin 系统来源 + * @param startTime 开始时间(可选) + * @param endTime 结束时间(可选) + * @return 用户充值统计列表(包含用户ID、总金额、订单数量) + */ + @PostMapping("/batchUserRechargeStatistics") + ResultResponse> batchUserRechargeStatistics( + @RequestBody List userIds, + @RequestParam("sysOrigin") String sysOrigin, + @RequestParam(value = "startTime", required = false) LocalDateTime startTime, + @RequestParam(value = "endTime", required = false) LocalDateTime endTime); } diff --git a/rc-service/rc-inner-api/order-inner/order-inner-model/src/main/java/com/red/circle/order/inner/model/dto/inapp/BatchUserRechargeStatisticsDTO.java b/rc-service/rc-inner-api/order-inner/order-inner-model/src/main/java/com/red/circle/order/inner/model/dto/inapp/BatchUserRechargeStatisticsDTO.java new file mode 100644 index 00000000..2ee20165 --- /dev/null +++ b/rc-service/rc-inner-api/order-inner/order-inner-model/src/main/java/com/red/circle/order/inner/model/dto/inapp/BatchUserRechargeStatisticsDTO.java @@ -0,0 +1,35 @@ +package com.red.circle.order.inner.model.dto.inapp; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; + +/** + * 批量用户充值统计 DTO. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Data +public class BatchUserRechargeStatisticsDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 用户ID. + */ + private Long userId; + + /** + * 支付成功的总金额(美元). + */ + private BigDecimal totalAmount; + + /** + * 支付成功的订单数量. + */ + private Integer orderCount; +} diff --git a/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/enums/team/TeamSalaryStatisticsCO.java b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/enums/team/TeamSalaryStatisticsCO.java new file mode 100644 index 00000000..34e73cb2 --- /dev/null +++ b/rc-service/rc-inner-api/other-inner/other-inner-model/src/main/java/com/red/circle/other/inner/enums/team/TeamSalaryStatisticsCO.java @@ -0,0 +1,31 @@ +package com.red.circle.other.inner.enums.team; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.util.List; + +/** + * 团队工资统计 CO. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Data +public class TeamSalaryStatisticsCO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * 团队成员用户ID列表. + */ + private List userIds; + + /** + * 工资总金额(美元). + */ + private BigDecimal totalSalary; +} diff --git a/rc-service/rc-inner-api/wallet-inner/wallet-inner-model/src/main/java/com/red/circle/wallet/inner/model/enums/UserBankWaterEvent.java b/rc-service/rc-inner-api/wallet-inner/wallet-inner-model/src/main/java/com/red/circle/wallet/inner/model/enums/UserBankWaterEvent.java index e4ae0681..9980fd9b 100644 --- a/rc-service/rc-inner-api/wallet-inner/wallet-inner-model/src/main/java/com/red/circle/wallet/inner/model/enums/UserBankWaterEvent.java +++ b/rc-service/rc-inner-api/wallet-inner/wallet-inner-model/src/main/java/com/red/circle/wallet/inner/model/enums/UserBankWaterEvent.java @@ -141,6 +141,10 @@ public enum UserBankWaterEvent { * 邀新用户红包提现. */ NEW_USER_RED_PACKET_WITHDRAW("New user red packet withdraw"), + + BD_SALARY_SETTLEMENT("BD工资结算"), + + BD_LEADER_SALARY_SETTLEMENT("BD Leader工资结算"), ; private final String describe; diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/InAppPurchaseDetailsService.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/InAppPurchaseDetailsService.java index 7c589d9a..e0d91e39 100644 --- a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/InAppPurchaseDetailsService.java +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/InAppPurchaseDetailsService.java @@ -88,4 +88,19 @@ public interface InAppPurchaseDetailsService { List listAll(); void updateMetadata(String id, Map metadata); + + /** + * 批量统计用户充值. + * + * @param userIds 用户ID列表 + * @param sysOrigin 系统来源 + * @param startTime 开始时间(可选) + * @param endTime 结束时间(可选) + * @return 用户充值统计列表(按用户ID分组) + */ + List batchUserRechargeStatistics( + List userIds, + String sysOrigin, + LocalDateTime startTime, + LocalDateTime endTime); } diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/entity/assist/CountUserInAppPurchase.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/entity/assist/CountUserInAppPurchase.java index a6d82482..c116d5cc 100644 --- a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/entity/assist/CountUserInAppPurchase.java +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/entity/assist/CountUserInAppPurchase.java @@ -26,4 +26,9 @@ public class CountUserInAppPurchase implements Serializable { @JsonSerialize(using = ToStringSerializer.class) private BigDecimal amount; + /** + * 订单数量. + */ + private Integer orderCount; + } diff --git a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseDetailsServiceImpl.java b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseDetailsServiceImpl.java index 127ba467..d33abd86 100644 --- a/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseDetailsServiceImpl.java +++ b/rc-service/rc-service-order/order-infrastructure/src/main/java/com/red/circle/order/infra/database/mongo/order/impl/InAppPurchaseDetailsServiceImpl.java @@ -348,4 +348,57 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ InAppPurchaseDetails.class ); } + + @Override + public List batchUserRechargeStatistics( + List userIds, + String sysOrigin, + LocalDateTime startTime, + LocalDateTime endTime) { + + if (CollectionUtils.isEmpty(userIds)) { + return Lists.newArrayList(); + } + + // 构建查询条件 + Criteria criteria = Criteria.where("sysOrigin").is(sysOrigin) + .and("acceptUserId").in(userIds) + .and("status").is(InAppPurchaseStatus.SUCCESS); // 只统计支付成功的订单 + + // 如果指定了时间范围,添加时间条件 + if (Objects.nonNull(startTime) && Objects.nonNull(endTime)) { + criteria.andOperator( + Criteria.where("createTime").gte(startTime), + Criteria.where("createTime").lte(endTime) + ); + } + + // 使用 MongoDB 聚合管道按用户ID分组统计 + AggregationResults aggregationResults = mongoTemplate.aggregate( + Aggregation.newAggregation( + Aggregation.match(criteria), + Aggregation.group("acceptUserId") + .sum("amountUsd").as("totalAmount") + .count().as("orderCount"), + Aggregation.sort(Sort.by(Sort.Order.asc("_id"))) // 按用户ID排序 + ), + InAppPurchaseDetails.class, + BasicDBObject.class); + + List basicDBObjectList = aggregationResults.getMappedResults(); + + if (CollectionUtils.isEmpty(basicDBObjectList)) { + return Lists.newArrayList(); + } + + // 转换为 CountUserInAppPurchase 对象 + return basicDBObjectList.stream().map(obj -> new CountUserInAppPurchase() + .setUserId(obj.getLong("_id")) // MongoDB group 的 _id 就是 acceptUserId + .setAmount(obj.get("totalAmount") != null + ? new BigDecimal(obj.get("totalAmount").toString()) + : BigDecimal.ZERO) + .setOrderCount(obj.getInt("orderCount", 0)) + ) + .collect(Collectors.toList()); + } } diff --git a/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/convertor/InAppPurchaseDetailsInnerConvert.java b/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/convertor/InAppPurchaseDetailsInnerConvert.java index c233ca56..53bbb4c4 100644 --- a/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/convertor/InAppPurchaseDetailsInnerConvert.java +++ b/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/convertor/InAppPurchaseDetailsInnerConvert.java @@ -5,6 +5,7 @@ import com.red.circle.order.infra.database.mongo.order.entity.InAppPurchaseDetai import com.red.circle.order.infra.database.mongo.order.entity.assist.CountUserInAppPurchase; import com.red.circle.order.infra.database.mongo.order.query.InAppPurchaseDetailsQuery; import com.red.circle.order.inner.model.cmd.inapp.InAppPurchaseDetailsQryCmd; +import com.red.circle.order.inner.model.dto.inapp.BatchUserRechargeStatisticsDTO; import com.red.circle.order.inner.model.dto.inapp.CountUserInAppPurchaseDTO; import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO; import java.util.List; @@ -23,4 +24,29 @@ public interface InAppPurchaseDetailsInnerConvert { List toListInAppPurchaseDetailsDTO(List details); List toListCountUserInAppPurchase(List list); + + /** + * 转换为批量用户充值统计DTO列表. + * + * @param list CountUserInAppPurchase列表 + * @return BatchUserRechargeStatisticsDTO列表 + */ + List toBatchUserRechargeStatisticsDTOList(List list); + + /** + * 转换为批量用户充值统计DTO. + * + * @param source CountUserInAppPurchase + * @return BatchUserRechargeStatisticsDTO + */ + default BatchUserRechargeStatisticsDTO toBatchUserRechargeStatisticsDTO(CountUserInAppPurchase source) { + if (source == null) { + return null; + } + BatchUserRechargeStatisticsDTO dto = new BatchUserRechargeStatisticsDTO(); + dto.setUserId(source.getUserId()); + dto.setTotalAmount(source.getAmount()); + dto.setOrderCount(source.getOrderCount()); + return dto; + } } diff --git a/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/endpoint/InAppPurchaseDetailsClientEndpoint.java b/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/endpoint/InAppPurchaseDetailsClientEndpoint.java index b8c72e38..ee5386c5 100644 --- a/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/endpoint/InAppPurchaseDetailsClientEndpoint.java +++ b/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/endpoint/InAppPurchaseDetailsClientEndpoint.java @@ -3,6 +3,7 @@ package com.red.circle.order.inner.endpoint; import com.red.circle.framework.dto.ResultResponse; import com.red.circle.order.inner.endpoint.api.InAppPurchaseDetailsClientApi; import com.red.circle.order.inner.model.cmd.inapp.InAppPurchaseDetailsQryCmd; +import com.red.circle.order.inner.model.dto.inapp.BatchUserRechargeStatisticsDTO; import com.red.circle.order.inner.model.dto.inapp.CountUserInAppPurchaseDTO; import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO; import com.red.circle.order.inner.service.InAppPurchaseDetailsClientService; @@ -57,4 +58,16 @@ public class InAppPurchaseDetailsClientEndpoint implements InAppPurchaseDetailsC ); } + @Override + public ResultResponse> batchUserRechargeStatistics( + List userIds, + String sysOrigin, + LocalDateTime startTime, + LocalDateTime endTime) { + return ResultResponse.success( + inAppPurchaseDetailsClientService.batchUserRechargeStatistics( + userIds, sysOrigin, startTime, endTime) + ); + } + } diff --git a/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/service/InAppPurchaseDetailsClientService.java b/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/service/InAppPurchaseDetailsClientService.java index 5b94aef4..244dad2e 100644 --- a/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/service/InAppPurchaseDetailsClientService.java +++ b/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/service/InAppPurchaseDetailsClientService.java @@ -31,4 +31,19 @@ public interface InAppPurchaseDetailsClientService { List countUserInAppPurchase(String sysOrigin, LocalDateTime startTime, LocalDateTime endTime, Integer limit); + /** + * 批量查询用户充值统计. + * + * @param userIds 用户ID列表 + * @param sysOrigin 系统来源 + * @param startTime 开始时间(可选) + * @param endTime 结束时间(可选) + * @return 用户充值统计列表 + */ + List batchUserRechargeStatistics( + List userIds, + String sysOrigin, + LocalDateTime startTime, + LocalDateTime endTime); + } diff --git a/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/service/impl/InAppPurchaseDetailsClientServiceImpl.java b/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/service/impl/InAppPurchaseDetailsClientServiceImpl.java index d85d79b3..77124a7d 100644 --- a/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/service/impl/InAppPurchaseDetailsClientServiceImpl.java +++ b/rc-service/rc-service-order/order-inner-endpoint/src/main/java/com/red/circle/order/inner/service/impl/InAppPurchaseDetailsClientServiceImpl.java @@ -55,4 +55,19 @@ public class InAppPurchaseDetailsClientServiceImpl implements InAppPurchaseDetai return inAppPurchaseDetailsInnerConvert.toListCountUserInAppPurchase(countUserInAppPurchases); } + @Override + public List batchUserRechargeStatistics( + List userIds, + String sysOrigin, + LocalDateTime startTime, + LocalDateTime endTime) { + + List countUserInAppPurchases = + inAppPurchaseDetailsService.batchUserRechargeStatistics( + userIds, sysOrigin, startTime, endTime); + + return inAppPurchaseDetailsInnerConvert.toBatchUserRechargeStatisticsDTOList( + countUserInAppPurchases); + } + } diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdLeaderSalarySettlementTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdLeaderSalarySettlementTask.java new file mode 100644 index 00000000..e3386bbd --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdLeaderSalarySettlementTask.java @@ -0,0 +1,61 @@ +package com.red.circle.other.app.scheduler; + +import com.red.circle.component.redis.annotation.TaskCacheLock; +import com.red.circle.other.app.service.BdLeaderSalarySettlementService; +import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.time.ZoneId; + +/** + * BD Leader 工资结算定时任务. + * 每月1号和16号 0点执行半月结算 + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class BdLeaderSalarySettlementTask { + + private final BdLeaderSalarySettlementService bdLeaderSalarySettlementService; + + /** + * 每月1号和16号 0点执行半月结算. + */ + @Scheduled(cron = "0 0 0 1,16 * ?", zone = "Asia/Riyadh") + @TaskCacheLock(key = "BD_LEADER_SALARY_SETTLEMENT", expireSecond = 86400) + public void processBdLeaderSalarySettlement() { + long startTime = System.currentTimeMillis(); + log.warn("========== BD Leader工资结算定时任务开始 =========="); + + try { + // 判断是上半月还是下半月 + Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); + String billTitle = TeamBillCycleUtils.parseBillBelongToDateRangeStr(billBelong); + + log.warn("结算周期:{} - {}", billBelong, billTitle); + + // 执行 BD Leader 工资结算 + bdLeaderSalarySettlementService.processAllBdLeaderSettlement(billBelong, billTitle); + + log.warn("========== BD Leader工资结算定时任务完成,耗时:{} ms ==========", + System.currentTimeMillis() - startTime); + } catch (Exception e) { + log.error("========== BD Leader工资结算定时任务异常 ==========", e); + } + } + + /** + * 测试方法(手动触发). + */ + public void processBdLeaderSalarySettlementTest(Integer billBelong) { + String billTitle = TeamBillCycleUtils.parseBillBelongToDateRangeStr(billBelong); + bdLeaderSalarySettlementService.processAllBdLeaderSettlement(billBelong, billTitle); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdSalarySettlementTask.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdSalarySettlementTask.java new file mode 100644 index 00000000..81586951 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/scheduler/BdSalarySettlementTask.java @@ -0,0 +1,61 @@ +package com.red.circle.other.app.scheduler; + +import com.red.circle.component.redis.annotation.TaskCacheLock; +import com.red.circle.other.app.service.BdSalarySettlementService; +import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import java.time.LocalDate; +import java.time.ZoneId; + +/** + * BD 工资结算定时任务. + * 每月1号和16号 0点执行半月结算 + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Slf4j +@Component +@RequiredArgsConstructor +public class BdSalarySettlementTask { + + private final BdSalarySettlementService bdSalarySettlementService; + + /** + * 每月1号和16号 0点执行半月结算. + */ + @Scheduled(cron = "0 0 0 1,16 * ?", zone = "Asia/Riyadh") + @TaskCacheLock(key = "BD_SALARY_SETTLEMENT", expireSecond = 86400) + public void processBdSalarySettlement() { + long startTime = System.currentTimeMillis(); + log.warn("========== BD工资结算定时任务开始 =========="); + + try { + // 判断是上半月还是下半月 + Integer billBelong = TeamBillCycleUtils.getCalcBillBelong(); + String billTitle = TeamBillCycleUtils.parseBillBelongToDateRangeStr(billBelong); + + log.warn("结算周期:{} - {}", billBelong, billTitle); + + // 执行 BD 工资结算 + bdSalarySettlementService.processAllBdSettlement(billBelong, billTitle); + + log.warn("========== BD工资结算定时任务完成,耗时:{} ms ==========", + System.currentTimeMillis() - startTime); + } catch (Exception e) { + log.error("========== BD工资结算定时任务异常 ==========", e); + } + } + + /** + * 测试方法(手动触发). + */ + public void processBdSalarySettlementTest(Integer billBelong) { + String billTitle = TeamBillCycleUtils.parseBillBelongToDateRangeStr(billBelong); + bdSalarySettlementService.processAllBdSettlement(billBelong, billTitle); + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/BdLeaderSalarySettlementServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/BdLeaderSalarySettlementServiceImpl.java new file mode 100644 index 00000000..e44a2fff --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/BdLeaderSalarySettlementServiceImpl.java @@ -0,0 +1,274 @@ +package com.red.circle.other.app.service; + +import com.red.circle.common.business.core.enums.ReceiptType; +import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; +import com.red.circle.other.app.dto.clientobject.BdLeaderSalarySettlementCO; +import com.red.circle.other.infra.enums.BdSettlementStatus; +import com.red.circle.other.infra.database.mongo.entity.bd.BdLeaderSalaryPolicy; +import com.red.circle.other.infra.database.mongo.entity.bd.BdLeaderSalarySettlementRecord; +import com.red.circle.other.infra.database.mongo.entity.bd.BdSalarySettlementRecord; +import com.red.circle.other.infra.database.mongo.service.bd.BdLeaderSalaryPolicyService; +import com.red.circle.other.infra.database.mongo.service.bd.BdLeaderSalarySettlementRecordService; +import com.red.circle.other.infra.database.mongo.service.bd.BdSalarySettlementRecordService; +import com.red.circle.other.infra.database.rds.entity.team.BusinessDevelopmentBaseInfo; +import com.red.circle.other.infra.database.rds.service.team.BusinessDevelopmentBaseInfoService; +import com.red.circle.tool.core.collection.CollectionUtils; +import com.red.circle.tool.core.date.TimestampUtils; +import com.red.circle.tool.core.sequence.IdWorkerUtils; +import com.red.circle.wallet.inner.endpoint.bank.UserBankBalanceClient; +import com.red.circle.wallet.inner.endpoint.bank.UserBankRunningWaterClient; +import com.red.circle.wallet.inner.model.cmd.UserBankBalanceIncrCmd; +import com.red.circle.wallet.inner.model.dto.UserBankRunningWaterDTO; +import com.red.circle.wallet.inner.model.enums.UserBankWaterEvent; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * BD Leader 工资结算服务实现. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class BdLeaderSalarySettlementServiceImpl implements BdLeaderSalarySettlementService { + + private final BusinessDevelopmentBaseInfoService businessDevelopmentBaseInfoService; + private final BdSalarySettlementRecordService bdSalarySettlementRecordService; + private final BdLeaderSalaryPolicyService bdLeaderSalaryPolicyService; + private final BdLeaderSalarySettlementRecordService bdLeaderSalarySettlementRecordService; + private final UserBankBalanceClient userBankBalanceClient; + private final UserBankRunningWaterClient userBankRunningWaterClient; + + @Override + public void processAllBdLeaderSettlement(Integer billBelong, String billTitle) { + log.warn("开始处理所有 BD Leader 工资结算,结算周期:{}", billBelong); + + // 查询所有 BD Leader + List allBdLeaders = businessDevelopmentBaseInfoService.listAll(); + + if (CollectionUtils.isEmpty(allBdLeaders)) { + log.warn("没有找到任何 BD Leader 数据"); + return; + } + + // 按 BD Leader 用户 ID 分组(去重) + Map> bdLeaderGroupMap = allBdLeaders.stream() + .filter(info -> info.getBdLeadUserId() != null) + .collect(Collectors.groupingBy(BusinessDevelopmentBaseInfo::getBdLeadUserId)); + + log.warn("共找到 {} 个 BD Leader", bdLeaderGroupMap.size()); + + int successCount = 0; + int failCount = 0; + + // 遍历每个 BD Leader 进行结算 + for (Long bdLeaderUserId : bdLeaderGroupMap.keySet()) { + try { + processSingleBdLeaderSettlement(bdLeaderUserId, billBelong, billTitle); + successCount++; + } catch (Exception e) { + log.error("BD Leader {} 工资结算失败", bdLeaderUserId, e); + failCount++; + } + } + + log.warn("BD Leader 工资结算完成,成功:{},失败:{}", successCount, failCount); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public BdLeaderSalarySettlementCO processSingleBdLeaderSettlement(Long bdLeaderUserId, Integer billBelong, String billTitle) { + log.info("开始处理 BD Leader {} 的工资结算", bdLeaderUserId); + + // 1. 生成幂等业务号 + String bizNo = generateBizNo(bdLeaderUserId, billBelong); + + // 2. 幂等性检查 + if (bdLeaderSalarySettlementRecordService.existsByBizNo(bizNo)) { + log.warn("BD Leader {} 在周期 {} 已经结算过,跳过", bdLeaderUserId, billBelong); + BdLeaderSalarySettlementRecord existingRecord = bdLeaderSalarySettlementRecordService.getByBizNo(bizNo); + return convertToCO(existingRecord); + } + + // 3. 统计 BD Leader 的团队数据 + List bdUserIds = businessDevelopmentBaseInfoService.listBdUserIdsByLeaderId(bdLeaderUserId); + + BdLeaderSalarySettlementRecord record = new BdLeaderSalarySettlementRecord(); + record.setId(IdWorkerUtils.getIdStr()); + record.setTimeId(IdWorkerUtils.getId()); + record.setSysOrigin(SysOriginPlatformEnum.LIKEI.name()); + record.setBdLeaderUserId(bdLeaderUserId); + record.setBillBelong(billBelong); + record.setBillTitle(billTitle); + record.setBdNumber(bdUserIds.size()); + record.setBizNo(bizNo); + record.setStatus(BdSettlementStatus.PENDING); + + // 4. 统计所有 BD 的工资总和 + BigDecimal totalBdSalary = BigDecimal.ZERO; + for (Long bdUserId : bdUserIds) { + BdSalarySettlementRecord bdRecord = bdSalarySettlementRecordService.getByUserIdAndBillBelong(bdUserId, billBelong); + if (bdRecord != null && bdRecord.getSettlementSalary() != null) { + totalBdSalary = totalBdSalary.add(bdRecord.getSettlementSalary()); + } + } + record.setTeamSalaryAmount(totalBdSalary.setScale(2, RoundingMode.DOWN)); + + // 5. 匹配政策并计算工资 + calculateLeaderSalary(record); + + // 6. 保存结算记录 + bdLeaderSalarySettlementRecordService.create(record); + + // 7. 如果工资大于 0,则发放工资 + if (record.getSettlementSalary() != null && record.getSettlementSalary().compareTo(BigDecimal.ZERO) > 0) { + try { + // 调用钱包服务发放工资 + issueSalary(bdLeaderUserId, record); + + // 更新状态为成功 + bdLeaderSalarySettlementRecordService.updateStatus(record.getId(), BdSettlementStatus.SUCCESS, null); + record.setStatus(BdSettlementStatus.SUCCESS); + + log.info("BD Leader {} 工资发放成功,金额:{} USD", bdLeaderUserId, record.getSettlementSalary()); + } catch (Exception e) { + log.error("BD Leader {} 工资发放失败", bdLeaderUserId, e); + bdLeaderSalarySettlementRecordService.updateStatus(record.getId(), BdSettlementStatus.FAILED, e.getMessage()); + record.setStatus(BdSettlementStatus.FAILED); + record.setFailureReason(e.getMessage()); + throw e; + } + } else { + log.info("BD Leader {} 本周期未达到结算标准,无工资发放", bdLeaderUserId); + } + + return convertToCO(record); + } + + /** + * 匹配政策并计算 BD Leader 工资. + */ + private void calculateLeaderSalary(BdLeaderSalarySettlementRecord record) { + // 获取所有启用的政策 + List policies = bdLeaderSalaryPolicyService.listEnabled(); + + if (CollectionUtils.isEmpty(policies)) { + record.setSettlementSalary(BigDecimal.ZERO); + return; + } + + // 按等级倒序排序,匹配最高等级 + policies.sort((p1, p2) -> p2.getLevel().compareTo(p1.getLevel())); + + for (BdLeaderSalaryPolicy policy : policies) { + // 必须同时满足 BD 数量和团队工资 + if (record.getBdNumber() >= policy.getRequiredBdNumber() + && record.getTeamSalaryAmount().compareTo(policy.getRequiredTeamSalary()) >= 0) { + + record.setHitLevel(policy.getLevel()); + record.setBasicSalary(policy.getBasicSalary()); + record.setRewardAmount(policy.getRewardAmount()); + + // 计算最终工资:Basic Salary + Reward Amount(如果满足奖励条件) + BigDecimal finalSalary = policy.getBasicSalary(); + + // 检查是否满足奖励条件(本周期有新增 BD) + boolean rewardEligible = checkRewardEligibility(record.getBdLeaderUserId(), record.getBillBelong()); + record.setRewardEligible(rewardEligible); + + if (rewardEligible && policy.getRewardAmount() != null) { + finalSalary = finalSalary.add(policy.getRewardAmount()); + } + + record.setSettlementSalary(finalSalary.setScale(2, RoundingMode.DOWN)); + return; + } + } + + // 未匹配到任何政策 + record.setSettlementSalary(BigDecimal.ZERO); + } + + /** + * 检查是否满足奖励条件(本周期是否有新增 BD). + */ + private boolean checkRewardEligibility(Long bdLeaderUserId, Integer billBelong) { + // TODO: 实现逻辑 - 检查本周期是否有新增 BD + // 目前简化处理:假设只要有 BD 就满足条件 + List bdUserIds = businessDevelopmentBaseInfoService.listBdUserIdsByLeaderId(bdLeaderUserId); + return !CollectionUtils.isEmpty(bdUserIds); + } + + /** + * 发放工资到美元账户. + */ + private void issueSalary(Long bdLeaderUserId, BdLeaderSalarySettlementRecord record) { + // 1. 增加美元余额 + userBankBalanceClient.incr(new UserBankBalanceIncrCmd() + .setUserId(bdLeaderUserId) + .setSysOrigin(record.getSysOrigin()) + .setAmount(com.red.circle.tool.core.tuple.PennyAmount.ofDollar(record.getSettlementSalary())) + ); + + // 2. 插入流水记录 + String remark = String.format("结算周期:%s,等级:%d,基础工资:%s USD", + record.getBillTitle(), record.getHitLevel(), record.getBasicSalary()); + if (record.getRewardEligible()) { + remark += String.format(",奖励:%s USD", record.getRewardAmount()); + } + + userBankRunningWaterClient.add(new UserBankRunningWaterDTO() + .setId(IdWorkerUtils.getId()) + .setUserId(bdLeaderUserId) + .setSysOrigin(record.getSysOrigin()) + .setType(ReceiptType.INCOME.getType()) + .setAmount(record.getSettlementSalary()) + .setEvent(UserBankWaterEvent.BD_LEADER_SALARY_SETTLEMENT.name()) + .setEventDescribe("BD Leader工资结算") + .setTrackId(record.getId()) + .setRemark(remark) + .setBalance(userBankBalanceClient.getBalance(bdLeaderUserId).getBody().getAccurateBalance()) + .setCreateTime(TimestampUtils.now()) + .setCreateUser(0L) + .setCreateUserOrigin(0) + ); + } + + /** + * 生成业务号(幂等性保证). + */ + private String generateBizNo(Long bdLeaderUserId, Integer billBelong) { + return String.format("BD_LEADER_SALARY_%d_%d", bdLeaderUserId, billBelong); + } + + /** + * 转换为 CO 对象. + */ + private BdLeaderSalarySettlementCO convertToCO(BdLeaderSalarySettlementRecord record) { + BdLeaderSalarySettlementCO co = new BdLeaderSalarySettlementCO(); + co.setBdLeaderUserId(record.getBdLeaderUserId()); + co.setBillBelong(record.getBillBelong()); + co.setBillTitle(record.getBillTitle()); + co.setBdNumber(record.getBdNumber()); + co.setTeamSalaryAmount(record.getTeamSalaryAmount()); + co.setHitLevel(record.getHitLevel()); + co.setBasicSalary(record.getBasicSalary()); + co.setRewardAmount(record.getRewardAmount()); + co.setRewardEligible(record.getRewardEligible()); + co.setNewRechargeAgentCount(record.getNewRechargeAgentCount()); + co.setSettlementSalary(record.getSettlementSalary()); + co.setStatus(record.getStatus().name()); + co.setFailureReason(record.getFailureReason()); + return co; + } +} diff --git a/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/BdSalarySettlementServiceImpl.java b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/BdSalarySettlementServiceImpl.java new file mode 100644 index 00000000..f0747f69 --- /dev/null +++ b/rc-service/rc-service-other/other-application/src/main/java/com/red/circle/other/app/service/BdSalarySettlementServiceImpl.java @@ -0,0 +1,365 @@ +package com.red.circle.other.app.service; + +import com.red.circle.common.business.core.enums.ReceiptType; +import com.red.circle.common.business.core.enums.SysOriginPlatformEnum; +import com.red.circle.other.app.dto.clientobject.BdSalarySettlementCO; +import com.red.circle.other.app.dto.clientobject.BdTeamStatisticsCO; +import com.red.circle.other.infra.enums.BdPolicyType; +import com.red.circle.other.infra.enums.BdSettlementStatus; +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.service.bd.BdSalaryPolicyService; +import com.red.circle.other.infra.database.mongo.service.bd.BdSalarySettlementRecordService; +import com.red.circle.other.infra.database.mongo.service.team.team.TeamMemberTargetService; +import com.red.circle.other.infra.database.rds.entity.team.BusinessDevelopmentTeam; +import com.red.circle.other.infra.database.rds.service.team.BusinessDevelopmentTeamService; +import com.red.circle.other.inner.enums.team.TeamSalaryStatisticsCO; +import com.red.circle.tool.core.collection.CollectionUtils; +import com.red.circle.tool.core.date.TimestampUtils; +import com.red.circle.tool.core.sequence.IdWorkerUtils; +import com.red.circle.order.inner.endpoint.api.InAppPurchaseDetailsClientApi; +import com.red.circle.order.inner.model.dto.inapp.BatchUserRechargeStatisticsDTO; +import com.red.circle.wallet.inner.endpoint.bank.UserBankBalanceClient; +import com.red.circle.wallet.inner.endpoint.bank.UserBankRunningWaterClient; +import com.red.circle.wallet.inner.model.cmd.UserBankBalanceIncrCmd; +import com.red.circle.wallet.inner.model.dto.UserBankRunningWaterDTO; +import com.red.circle.wallet.inner.model.enums.UserBankWaterEvent; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.*; +import java.util.stream.Collectors; + +/** + * BD 工资结算服务实现. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class BdSalarySettlementServiceImpl implements BdSalarySettlementService { + + private final BusinessDevelopmentTeamService businessDevelopmentTeamService; + private final TeamMemberTargetService teamMemberTargetService; + private final BdSalaryPolicyService bdSalaryPolicyService; + private final BdSalarySettlementRecordService bdSalarySettlementRecordService; + private final UserBankBalanceClient userBankBalanceClient; + private final UserBankRunningWaterClient userBankRunningWaterClient; + private final InAppPurchaseDetailsClientApi inAppPurchaseDetailsClientApi; + + + @Override + public void processAllBdSettlement(Integer billBelong, String billTitle) { + log.warn("开始处理所有 BD 工资结算,结算周期:{}", billBelong); + + // 查询所有 BD + List allBdTeams = businessDevelopmentTeamService.listAll(); + + if (CollectionUtils.isEmpty(allBdTeams)) { + log.warn("没有找到任何 BD 数据"); + return; + } + + // 按 BD 用户 ID 分组(去重) + Map> bdGroupMap = allBdTeams.stream() + .collect(Collectors.groupingBy(BusinessDevelopmentTeam::getUserId)); + + log.warn("共找到 {} 个 BD", bdGroupMap.size()); + + int successCount = 0; + int failCount = 0; + + // 遍历每个 BD 进行结算 + for (Long bdUserId : bdGroupMap.keySet()) { + try { + processSingleBdSettlement(bdUserId, billBelong, billTitle); + successCount++; + } catch (Exception e) { + log.error("BD {} 工资结算失败", bdUserId, e); + failCount++; + } + } + + log.warn("BD 工资结算完成,成功:{},失败:{}", successCount, failCount); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public BdSalarySettlementCO processSingleBdSettlement(Long bdUserId, Integer billBelong, String billTitle) { + log.info("开始处理 BD {} 的工资结算", bdUserId); + + // 1. 生成幂等业务号 + String bizNo = generateBizNo(bdUserId, billBelong); + + // 2. 幂等性检查 + if (bdSalarySettlementRecordService.existsByBizNo(bizNo)) { + log.warn("BD {} 在周期 {} 已经结算过,跳过", bdUserId, billBelong); + BdSalarySettlementRecord existingRecord = bdSalarySettlementRecordService.getByBizNo(bizNo); + return convertToCO(existingRecord); + } + + // 3. 统计 BD 团队数据 + BdTeamStatisticsCO teamStatistics = statisticsBdTeam(bdUserId, billBelong); + + // 4. 匹配政策并计算工资 + BdSalarySettlementRecord record = calculateSalary(bdUserId, billBelong, billTitle, teamStatistics, bizNo); + + // 5. 保存结算记录 + bdSalarySettlementRecordService.create(record); + + // 6. 如果工资大于 0,则发放工资 + if (record.getSettlementSalary() != null && record.getSettlementSalary().compareTo(BigDecimal.ZERO) > 0) { + try { + // 调用钱包服务发放工资 + issueSalary(bdUserId, record); + + // 更新状态为成功 + bdSalarySettlementRecordService.updateStatus(record.getId(), BdSettlementStatus.SUCCESS, null); + record.setStatus(BdSettlementStatus.SUCCESS); + + log.info("BD {} 工资发放成功,金额:{} USD", bdUserId, record.getSettlementSalary()); + } catch (Exception e) { + log.error("BD {} 工资发放失败", bdUserId, e); + bdSalarySettlementRecordService.updateStatus(record.getId(), BdSettlementStatus.FAILED, e.getMessage()); + record.setStatus(BdSettlementStatus.FAILED); + record.setFailureReason(e.getMessage()); + throw e; + } + } else { + log.info("BD {} 本周期未达到结算标准,无工资发放", bdUserId); + } + + return convertToCO(record); + } + + /** + * 统计 BD 的团队数据. + */ + private BdTeamStatisticsCO statisticsBdTeam(Long bdUserId, Integer billBelong) { + // 查询 BD 下的所有 Agent 团队 + List bdTeams = businessDevelopmentTeamService.listByBdUserId(bdUserId); + + BdTeamStatisticsCO statistics = new BdTeamStatisticsCO(); + statistics.setBdUserId(bdUserId); + statistics.setAgencyNumber(bdTeams.size()); + + if (CollectionUtils.isEmpty(bdTeams)) { + statistics.setTeamSalaryAmount(BigDecimal.ZERO); + statistics.setTeamRechargeAmount(BigDecimal.ZERO); + return statistics; + } + + // 统计团队工资总和和收集所有用户ID + BigDecimal totalSalary = BigDecimal.ZERO; + List allUserIds = new ArrayList<>(); + + for (BusinessDevelopmentTeam bdTeam : bdTeams) { + // 从 TeamMemberTarget 统计该团队的工资和用户ID + TeamSalaryStatisticsCO teamStatistics = + teamMemberTargetService.statisticsTeamSalaryByTeamIdAndBillBelong( + bdTeam.getTeamId(), billBelong); + + totalSalary = totalSalary.add(teamStatistics.getTotalSalary()); + allUserIds.addAll(teamStatistics.getUserIds()); + } + statistics.setTeamSalaryAmount(totalSalary.setScale(2, RoundingMode.DOWN)); + + // 统计团队充值总和(从订单服务统计) + BigDecimal totalRecharge = statisticsTeamRecharge(allUserIds, billBelong); + statistics.setTeamRechargeAmount(totalRecharge.setScale(2, RoundingMode.DOWN)); + + return statistics; + } + + /** + * 统计团队充值总和. + * + * @param userIds 用户ID列表 + * @param billBelong 结算周期(格式:yyyyMM) + * @return 充值总金额(美元) + */ + private BigDecimal statisticsTeamRecharge(List userIds, Integer billBelong) { + if (CollectionUtils.isEmpty(userIds)) { + return BigDecimal.ZERO; + } + + try { + // 计算结算周期的开始和结束时间 + int year = billBelong / 100; + int month = billBelong % 100; + + java.time.LocalDateTime startTime = java.time.LocalDateTime.of(year, month, 1, 0, 0, 0); + java.time.LocalDateTime endTime = startTime.plusMonths(1).minusSeconds(1); + + // 调用订单服务批量查询用户充值统计 + com.red.circle.framework.dto.ResultResponse> response = + inAppPurchaseDetailsClientApi.batchUserRechargeStatistics( + userIds, + SysOriginPlatformEnum.LIKEI.name(), + startTime, + endTime + ); + + if (response == null || !response.getStatus() || CollectionUtils.isEmpty(response.getBody())) { + log.warn("查询用户充值统计失败或无数据,userIds: {}, billBelong: {}", userIds.size(), billBelong); + return BigDecimal.ZERO; + } + + // 统计所有用户的充值总和 + BigDecimal totalRecharge = response.getBody().stream() + .map(BatchUserRechargeStatisticsDTO::getTotalAmount) + .filter(Objects::nonNull) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + log.info("统计团队充值成功,用户数: {}, 结算周期: {}, 充值总额: {} USD", + userIds.size(), billBelong, totalRecharge); + + return totalRecharge; + + } catch (Exception e) { + log.error("统计团队充值失败,userIds: {}, billBelong: {}", userIds.size(), billBelong, e); + return BigDecimal.ZERO; + } + } + + /** + * 匹配政策并计算工资. + */ + private BdSalarySettlementRecord calculateSalary(Long bdUserId, Integer billBelong, String billTitle, + BdTeamStatisticsCO teamStatistics, String bizNo) { + BdSalarySettlementRecord record = new BdSalarySettlementRecord(); + record.setId(IdWorkerUtils.getIdStr()); + record.setTimeId(IdWorkerUtils.getId()); + record.setSysOrigin(SysOriginPlatformEnum.LIKEI.name()); + record.setBdUserId(bdUserId); + record.setBillBelong(billBelong); + record.setBillTitle(billTitle); + record.setAgencyNumber(teamStatistics.getAgencyNumber()); + record.setTeamSalaryAmount(teamStatistics.getTeamSalaryAmount()); + record.setTeamRechargeAmount(teamStatistics.getTeamRechargeAmount()); + record.setBizNo(bizNo); + record.setStatus(BdSettlementStatus.PENDING); + + // 获取所有启用的政策 + List salaryPolicies = bdSalaryPolicyService.listEnabledByType(BdPolicyType.TEAM_SALARY); + List rechargePolicies = bdSalaryPolicyService.listEnabledByType(BdPolicyType.TEAM_RECHARGE); + + // 匹配政策1:Team Salary Policy + BigDecimal salarySalary = matchAndCalculate(salaryPolicies, teamStatistics.getAgencyNumber(), + teamStatistics.getTeamSalaryAmount(), BdPolicyType.TEAM_SALARY, record); + + // 匹配政策2:Team Recharge Policy + BigDecimal rechargeSalary = matchAndCalculate(rechargePolicies, teamStatistics.getAgencyNumber(), + teamStatistics.getTeamRechargeAmount(), BdPolicyType.TEAM_RECHARGE, record); + + // 选择最高工资 + if (salarySalary.compareTo(rechargeSalary) >= 0) { + record.setHitPolicyType(BdPolicyType.TEAM_SALARY); + record.setSettlementSalary(salarySalary); + } else { + record.setHitPolicyType(BdPolicyType.TEAM_RECHARGE); + record.setSettlementSalary(rechargeSalary); + } + + return record; + } + + /** + * 匹配政策并计算工资. + */ + private BigDecimal matchAndCalculate(List policies, Integer agencyNumber, + BigDecimal teamAmount, BdPolicyType policyType, + BdSalarySettlementRecord record) { + if (CollectionUtils.isEmpty(policies)) { + return BigDecimal.ZERO; + } + + // 按等级倒序排序,匹配最高等级 + policies.sort((p1, p2) -> p2.getLevel().compareTo(p1.getLevel())); + + for (BdSalaryPolicy policy : policies) { + // 必须同时满足 Agent 数量和团队金额 + if (agencyNumber >= policy.getRequiredAgencyNumber() + && teamAmount.compareTo(policy.getRequiredTeamAmount()) >= 0) { + + // 计算工资:团队金额 * 提成比例 + BigDecimal salary = teamAmount.multiply(policy.getCommissionRate()) + .divide(new BigDecimal("100"), 2, RoundingMode.DOWN); + + // 记录命中的政策信息(如果这个政策更优) + if (policyType == BdPolicyType.TEAM_SALARY && + (record.getHitPolicyType() == null || record.getHitPolicyType() == BdPolicyType.TEAM_SALARY)) { + record.setHitLevel(policy.getLevel()); + record.setCommissionRate(policy.getCommissionRate()); + } + + return salary; + } + } + + return BigDecimal.ZERO; + } + + /** + * 发放工资到美元账户. + */ + private void issueSalary(Long bdUserId, BdSalarySettlementRecord record) { + // 1. 增加美元余额 + userBankBalanceClient.incr(new UserBankBalanceIncrCmd() + .setUserId(bdUserId) + .setSysOrigin(record.getSysOrigin()) + .setAmount(com.red.circle.tool.core.tuple.PennyAmount.ofDollar(record.getSettlementSalary())) + ); + + // 2. 插入流水记录 + userBankRunningWaterClient.add(new UserBankRunningWaterDTO() + .setId(IdWorkerUtils.getId()) + .setUserId(bdUserId) + .setSysOrigin(record.getSysOrigin()) + .setType(ReceiptType.INCOME.getType()) + .setAmount(record.getSettlementSalary()) + .setEvent(UserBankWaterEvent.BD_SALARY_SETTLEMENT.name()) + .setEventDescribe("BD工资结算") + .setTrackId(record.getId()) + .setRemark(String.format("结算周期:%s,等级:%d,提成比例:%s%%", + record.getBillTitle(), record.getHitLevel(), record.getCommissionRate())) + .setBalance(userBankBalanceClient.getBalance(bdUserId).getBody().getAccurateBalance()) + .setCreateTime(TimestampUtils.now()) + .setCreateUser(0L) + .setCreateUserOrigin(0) + ); + } + + /** + * 生成业务号(幂等性保证). + */ + private String generateBizNo(Long bdUserId, Integer billBelong) { + return String.format("BD_SALARY_%d_%d", bdUserId, billBelong); + } + + /** + * 转换为 CO 对象. + */ + private BdSalarySettlementCO convertToCO(BdSalarySettlementRecord record) { + BdSalarySettlementCO co = new BdSalarySettlementCO(); + co.setBdUserId(record.getBdUserId()); + co.setBillBelong(record.getBillBelong()); + co.setBillTitle(record.getBillTitle()); + co.setAgencyNumber(record.getAgencyNumber()); + co.setTeamSalaryAmount(record.getTeamSalaryAmount()); + co.setTeamRechargeAmount(record.getTeamRechargeAmount()); + co.setHitPolicyType(record.getHitPolicyType().name()); + co.setHitLevel(record.getHitLevel()); + co.setCommissionRate(record.getCommissionRate()); + co.setSettlementSalary(record.getSettlementSalary()); + co.setStatus(record.getStatus().name()); + co.setFailureReason(record.getFailureReason()); + return co; + } +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdLeaderPolicyInfoCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdLeaderPolicyInfoCO.java new file mode 100644 index 00000000..33e9ecb1 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdLeaderPolicyInfoCO.java @@ -0,0 +1,40 @@ +package com.red.circle.other.app.dto.clientobject; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * BD Leader 政策信息. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Data +public class BdLeaderPolicyInfoCO { + + /** + * 等级. + */ + private Integer level; + + /** + * 所需 BD 数量. + */ + private Integer requiredBdNumber; + + /** + * 团队工资要求(美元). + */ + private BigDecimal requiredTeamSalary; + + /** + * 基础工资(美元). + */ + private BigDecimal basicSalary; + + /** + * 奖励金额(美元). + */ + private BigDecimal rewardAmount; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdLeaderSalarySettlementCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdLeaderSalarySettlementCO.java new file mode 100644 index 00000000..b0f1f330 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdLeaderSalarySettlementCO.java @@ -0,0 +1,80 @@ +package com.red.circle.other.app.dto.clientobject; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * BD Leader 工资结算结果. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Data +public class BdLeaderSalarySettlementCO { + + /** + * BD Leader 用户 ID. + */ + private Long bdLeaderUserId; + + /** + * 结算周期. + */ + private Integer billBelong; + + /** + * 结算周期标题. + */ + private String billTitle; + + /** + * BD 数量. + */ + private Integer bdNumber; + + /** + * 团队工资总和(美元). + */ + private BigDecimal teamSalaryAmount; + + /** + * 命中的等级. + */ + private Integer hitLevel; + + /** + * 基础工资(美元). + */ + private BigDecimal basicSalary; + + /** + * 奖励金额(美元). + */ + private BigDecimal rewardAmount; + + /** + * 是否获得奖励. + */ + private Boolean rewardEligible; + + /** + * 本周期新增充值代理数量. + */ + private Integer newRechargeAgentCount; + + /** + * 最终结算工资(美元). + */ + private BigDecimal settlementSalary; + + /** + * 结算状态. + */ + private String status; + + /** + * 失败原因. + */ + private String failureReason; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdPolicyInfoCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdPolicyInfoCO.java new file mode 100644 index 00000000..e072ce27 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdPolicyInfoCO.java @@ -0,0 +1,40 @@ +package com.red.circle.other.app.dto.clientobject; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * BD 政策信息. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Data +public class BdPolicyInfoCO { + + /** + * 政策类型. + */ + private String policyType; + + /** + * 等级. + */ + private Integer level; + + /** + * 所需 Agent 数量. + */ + private Integer requiredAgencyNumber; + + /** + * 团队金额要求(美元). + */ + private BigDecimal requiredTeamAmount; + + /** + * 提成比例(百分比). + */ + private BigDecimal commissionRate; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdSalarySettlementCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdSalarySettlementCO.java new file mode 100644 index 00000000..f8ea0e93 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdSalarySettlementCO.java @@ -0,0 +1,75 @@ +package com.red.circle.other.app.dto.clientobject; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * BD 工资结算结果. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Data +public class BdSalarySettlementCO { + + /** + * BD 用户 ID. + */ + private Long bdUserId; + + /** + * 结算周期. + */ + private Integer billBelong; + + /** + * 结算周期标题. + */ + private String billTitle; + + /** + * Agent 数量. + */ + private Integer agencyNumber; + + /** + * 团队工资总和(美元). + */ + private BigDecimal teamSalaryAmount; + + /** + * 团队充值总和(美元). + */ + private BigDecimal teamRechargeAmount; + + /** + * 命中的政策类型. + */ + private String hitPolicyType; + + /** + * 命中的等级. + */ + private Integer hitLevel; + + /** + * 提成比例(百分比). + */ + private BigDecimal commissionRate; + + /** + * 最终结算工资(美元). + */ + private BigDecimal settlementSalary; + + /** + * 结算状态. + */ + private String status; + + /** + * 失败原因. + */ + private String failureReason; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdTeamStatisticsCO.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdTeamStatisticsCO.java new file mode 100644 index 00000000..109b6136 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/dto/clientobject/BdTeamStatisticsCO.java @@ -0,0 +1,35 @@ +package com.red.circle.other.app.dto.clientobject; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * BD 团队统计信息. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Data +public class BdTeamStatisticsCO { + + /** + * BD 用户 ID. + */ + private Long bdUserId; + + /** + * Agent 数量. + */ + private Integer agencyNumber; + + /** + * 团队工资总和(美元). + */ + private BigDecimal teamSalaryAmount; + + /** + * 团队充值总和(美元). + */ + private BigDecimal teamRechargeAmount; +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/BdLeaderSalarySettlementService.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/BdLeaderSalarySettlementService.java new file mode 100644 index 00000000..b3645d3f --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/BdLeaderSalarySettlementService.java @@ -0,0 +1,30 @@ +package com.red.circle.other.app.service; + +import com.red.circle.other.app.dto.clientobject.BdLeaderSalarySettlementCO; + +/** + * BD Leader 工资结算服务. + * + * @author AI Assistant + * @since 2025-10-29 + */ +public interface BdLeaderSalarySettlementService { + + /** + * 处理所有 BD Leader 的工资结算. + * + * @param billBelong 结算周期 + * @param billTitle 结算周期标题 + */ + void processAllBdLeaderSettlement(Integer billBelong, String billTitle); + + /** + * 处理单个 BD Leader 的工资结算. + * + * @param bdLeaderUserId BD Leader 用户 ID + * @param billBelong 结算周期 + * @param billTitle 结算周期标题 + * @return 结算结果 + */ + BdLeaderSalarySettlementCO processSingleBdLeaderSettlement(Long bdLeaderUserId, Integer billBelong, String billTitle); +} diff --git a/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/BdSalarySettlementService.java b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/BdSalarySettlementService.java new file mode 100644 index 00000000..fbe74d04 --- /dev/null +++ b/rc-service/rc-service-other/other-client/src/main/java/com/red/circle/other/app/service/BdSalarySettlementService.java @@ -0,0 +1,30 @@ +package com.red.circle.other.app.service; + +import com.red.circle.other.app.dto.clientobject.BdSalarySettlementCO; + +/** + * BD 工资结算服务. + * + * @author AI Assistant + * @since 2025-10-29 + */ +public interface BdSalarySettlementService { + + /** + * 处理所有 BD 的工资结算. + * + * @param billBelong 结算周期 + * @param billTitle 结算周期标题 + */ + void processAllBdSettlement(Integer billBelong, String billTitle); + + /** + * 处理单个 BD 的工资结算. + * + * @param bdUserId BD 用户 ID + * @param billBelong 结算周期 + * @param billTitle 结算周期标题 + * @return 结算结果 + */ + BdSalarySettlementCO processSingleBdSettlement(Long bdUserId, Integer billBelong, String billTitle); +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdLeaderSalaryPolicy.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdLeaderSalaryPolicy.java new file mode 100644 index 00000000..ff701820 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdLeaderSalaryPolicy.java @@ -0,0 +1,72 @@ +package com.red.circle.other.infra.database.mongo.entity.bd; + +import lombok.Data; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.sql.Timestamp; + +/** + * BD Leader 工资政策配置. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Data +@Accessors(chain = true) +@Document("bd_leader_salary_policy") +public class BdLeaderSalaryPolicy implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID. + */ + @Id + private String id; + + /** + * 等级. + */ + private Integer level; + + /** + * 所需 BD 数量. + */ + private Integer requiredBdNumber; + + /** + * 团队工资要求(美元)- 所有 BD 的工资总和. + */ + private BigDecimal requiredTeamSalary; + + /** + * 基础工资(美元)- Basic Salary. + */ + private BigDecimal basicSalary; + + /** + * 奖励金额(美元)- Reward Amount. + */ + private BigDecimal rewardAmount; + + /** + * 是否启用. + */ + private Boolean enabled; + + /** + * 创建时间. + */ + private Timestamp createTime; + + /** + * 更新时间. + */ + private Timestamp updateTime; +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdLeaderSalarySettlementRecord.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdLeaderSalarySettlementRecord.java new file mode 100644 index 00000000..ec7c7472 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdLeaderSalarySettlementRecord.java @@ -0,0 +1,127 @@ +package com.red.circle.other.infra.database.mongo.entity.bd; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.red.circle.other.infra.enums.BdSettlementStatus; +import lombok.Data; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.sql.Timestamp; + +/** + * BD Leader 工资结算记录. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Data +@Accessors(chain = true) +@Document("bd_leader_salary_settlement_record") +public class BdLeaderSalarySettlementRecord implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID. + */ + @Id + private String id; + + /** + * 时序 ID. + */ + @JsonSerialize(using = ToStringSerializer.class) + private Long timeId; + + /** + * 系统来源. + */ + private String sysOrigin; + + /** + * BD Leader 用户 ID. + */ + @JsonSerialize(using = ToStringSerializer.class) + private Long bdLeaderUserId; + + /** + * 结算周期(格式:202501 表示2025年1月上半月,202502 表示2025年1月下半月). + */ + private Integer billBelong; + + /** + * 结算周期标题(如:2025年1月上半月). + */ + private String billTitle; + + /** + * BD 数量. + */ + private Integer bdNumber; + + /** + * 团队工资总和(美元)- 所有 BD 的工资总和. + */ + private BigDecimal teamSalaryAmount; + + /** + * 命中的等级. + */ + private Integer hitLevel; + + /** + * 基础工资(美元)- Basic Salary. + */ + private BigDecimal basicSalary; + + /** + * 奖励金额(美元)- Reward Amount. + */ + private BigDecimal rewardAmount; + + /** + * 是否满足奖励条件(本周期是否带来新充值代理). + */ + private Boolean rewardEligible; + + /** + * 本周期新增充值代理数量. + */ + private Integer newRechargeAgentCount; + + /** + * 最终结算工资(美元)- Basic Salary + Reward Amount(如符合条件). + */ + private BigDecimal settlementSalary; + + /** + * 结算状态:PENDING | SUCCESS | FAILED. + */ + private BdSettlementStatus status; + + /** + * 失败原因. + */ + private String failureReason; + + /** + * 幂等业务号(防重). + */ + private String bizNo; + + /** + * 创建时间. + */ + private Timestamp createTime; + + /** + * 更新时间. + */ + private Timestamp updateTime; +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdSalaryPolicy.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdSalaryPolicy.java new file mode 100644 index 00000000..f1615efe --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdSalaryPolicy.java @@ -0,0 +1,75 @@ +package com.red.circle.other.infra.database.mongo.entity.bd; + +import com.red.circle.other.infra.enums.BdPolicyType; +import lombok.Data; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.sql.Timestamp; + +/** + * BD 工资政策配置. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Data +@Accessors(chain = true) +@Document("bd_salary_policy") +public class BdSalaryPolicy implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID. + */ + @Id + private String id; + + /** + * 政策类型:TEAM_SALARY | TEAM_RECHARGE. + */ + private BdPolicyType policyType; + + /** + * 等级. + */ + private Integer level; + + /** + * 所需 Agent 数量. + */ + private Integer requiredAgencyNumber; + + /** + * 团队金额要求(美元). + * - 当 policyType = TEAM_SALARY 时,表示团队工资总和要求 + * - 当 policyType = TEAM_RECHARGE 时,表示团队充值总和要求 + */ + private BigDecimal requiredTeamAmount; + + /** + * 提成比例(百分比,如 3% 存储为 3.00). + */ + private BigDecimal commissionRate; + + /** + * 是否启用. + */ + private Boolean enabled; + + /** + * 创建时间. + */ + private Timestamp createTime; + + /** + * 更新时间. + */ + private Timestamp updateTime; +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdSalarySettlementRecord.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdSalarySettlementRecord.java new file mode 100644 index 00000000..327da25c --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/entity/bd/BdSalarySettlementRecord.java @@ -0,0 +1,123 @@ +package com.red.circle.other.infra.database.mongo.entity.bd; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.red.circle.other.infra.enums.BdPolicyType; +import com.red.circle.other.infra.enums.BdSettlementStatus; +import lombok.Data; +import lombok.experimental.Accessors; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.io.Serial; +import java.io.Serializable; +import java.math.BigDecimal; +import java.sql.Timestamp; + +/** + * BD 工资结算记录. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Data +@Accessors(chain = true) +@Document("bd_salary_settlement_record") +public class BdSalarySettlementRecord implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** + * ID. + */ + @Id + private String id; + + /** + * 时序 ID. + */ + @JsonSerialize(using = ToStringSerializer.class) + private Long timeId; + + /** + * 系统来源. + */ + private String sysOrigin; + + /** + * BD 用户 ID. + */ + @JsonSerialize(using = ToStringSerializer.class) + private Long bdUserId; + + /** + * 结算周期(格式:202501 表示2025年1月上半月,202502 表示2025年1月下半月). + */ + private Integer billBelong; + + /** + * 结算周期标题(如:2025年1月上半月). + */ + private String billTitle; + + /** + * Agent 数量. + */ + private Integer agencyNumber; + + /** + * 团队工资总和(美元). + */ + private BigDecimal teamSalaryAmount; + + /** + * 团队充值总和(美元). + */ + private BigDecimal teamRechargeAmount; + + /** + * 命中的政策类型:TEAM_SALARY | TEAM_RECHARGE. + */ + private BdPolicyType hitPolicyType; + + /** + * 命中的等级. + */ + private Integer hitLevel; + + /** + * 提成比例(百分比). + */ + private BigDecimal commissionRate; + + /** + * 最终结算工资(美元). + */ + private BigDecimal settlementSalary; + + /** + * 结算状态:PENDING | SUCCESS | FAILED. + */ + private BdSettlementStatus status; + + /** + * 失败原因. + */ + private String failureReason; + + /** + * 幂等业务号(防重). + */ + private String bizNo; + + /** + * 创建时间. + */ + private Timestamp createTime; + + /** + * 更新时间. + */ + private Timestamp updateTime; +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalaryPolicyService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalaryPolicyService.java new file mode 100644 index 00000000..816cf434 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalaryPolicyService.java @@ -0,0 +1,54 @@ +package com.red.circle.other.infra.database.mongo.service.bd; + +import com.red.circle.other.infra.database.mongo.entity.bd.BdLeaderSalaryPolicy; + +import java.util.List; + +/** + * BD Leader 政策服务. + * + * @author AI Assistant + * @since 2025-10-29 + */ +public interface BdLeaderSalaryPolicyService { + + /** + * 创建政策. + */ + void create(BdLeaderSalaryPolicy policy); + + /** + * 批量创建政策. + */ + void batchCreate(List policies); + + /** + * 更新政策. + */ + void update(BdLeaderSalaryPolicy policy); + + /** + * 根据 ID 查询. + */ + BdLeaderSalaryPolicy getById(String id); + + /** + * 查询所有启用的政策(按等级升序). + */ + List listEnabled(); + + /** + * 根据等级查询. + */ + BdLeaderSalaryPolicy getByLevel(Integer level); + + /** + * 启用政策. + */ + void enable(String id); + + /** + * 禁用政策. + */ + void disable(String id); +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalaryPolicyServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalaryPolicyServiceImpl.java new file mode 100644 index 00000000..b3c32715 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalaryPolicyServiceImpl.java @@ -0,0 +1,85 @@ +package com.red.circle.other.infra.database.mongo.service.bd; + +import com.red.circle.other.infra.database.mongo.entity.bd.BdLeaderSalaryPolicy; +import com.red.circle.tool.core.date.TimestampUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * BD Leader 政策服务实现. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Service +@RequiredArgsConstructor +public class BdLeaderSalaryPolicyServiceImpl implements BdLeaderSalaryPolicyService { + + private final MongoTemplate mongoTemplate; + + @Override + public void create(BdLeaderSalaryPolicy policy) { + policy.setCreateTime(TimestampUtils.now()); + policy.setUpdateTime(TimestampUtils.now()); + mongoTemplate.save(policy); + } + + @Override + public void batchCreate(List policies) { + policies.forEach(policy -> { + policy.setCreateTime(TimestampUtils.now()); + policy.setUpdateTime(TimestampUtils.now()); + }); + mongoTemplate.insertAll(policies); + } + + @Override + public void update(BdLeaderSalaryPolicy policy) { + policy.setUpdateTime(TimestampUtils.now()); + mongoTemplate.save(policy); + } + + @Override + public BdLeaderSalaryPolicy getById(String id) { + return mongoTemplate.findById(id, BdLeaderSalaryPolicy.class); + } + + @Override + public List listEnabled() { + Query query = new Query(Criteria.where("enabled").is(true)); + query.with(Sort.by(Sort.Order.asc("level"))); + return mongoTemplate.find(query, BdLeaderSalaryPolicy.class); + } + + @Override + public BdLeaderSalaryPolicy getByLevel(Integer level) { + Query query = new Query(Criteria.where("level").is(level) + .and("enabled").is(true)); + return mongoTemplate.findOne(query, BdLeaderSalaryPolicy.class); + } + + @Override + public void enable(String id) { + Query query = new Query(Criteria.where("_id").is(id)); + Update update = new Update() + .set("enabled", true) + .set("updateTime", TimestampUtils.now()); + mongoTemplate.updateFirst(query, update, BdLeaderSalaryPolicy.class); + } + + @Override + public void disable(String id) { + Query query = new Query(Criteria.where("_id").is(id)); + Update update = new Update() + .set("enabled", false) + .set("updateTime", TimestampUtils.now()); + mongoTemplate.updateFirst(query, update, BdLeaderSalaryPolicy.class); + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalarySettlementRecordService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalarySettlementRecordService.java new file mode 100644 index 00000000..ac96c726 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalarySettlementRecordService.java @@ -0,0 +1,55 @@ +package com.red.circle.other.infra.database.mongo.service.bd; + +import com.red.circle.other.infra.database.mongo.entity.bd.BdLeaderSalarySettlementRecord; +import com.red.circle.other.infra.enums.BdSettlementStatus; + +import java.util.List; + +/** + * BD Leader 结算记录服务. + * + * @author AI Assistant + * @since 2025-10-29 + */ +public interface BdLeaderSalarySettlementRecordService { + + /** + * 创建结算记录. + */ + void create(BdLeaderSalarySettlementRecord record); + + /** + * 更新结算记录. + */ + void update(BdLeaderSalarySettlementRecord record); + + /** + * 根据 ID 查询. + */ + BdLeaderSalarySettlementRecord getById(String id); + + /** + * 根据业务号查询(幂等性检查). + */ + BdLeaderSalarySettlementRecord getByBizNo(String bizNo); + + /** + * 检查业务号是否已存在. + */ + boolean existsByBizNo(String bizNo); + + /** + * 根据 BD Leader 用户 ID 和结算周期查询. + */ + BdLeaderSalarySettlementRecord getByUserIdAndBillBelong(Long bdLeaderUserId, Integer billBelong); + + /** + * 根据结算周期查询所有记录. + */ + List listByBillBelong(Integer billBelong); + + /** + * 更新结算状态. + */ + void updateStatus(String id, BdSettlementStatus status, String failureReason); +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalarySettlementRecordServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalarySettlementRecordServiceImpl.java new file mode 100644 index 00000000..2ed88de4 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdLeaderSalarySettlementRecordServiceImpl.java @@ -0,0 +1,79 @@ +package com.red.circle.other.infra.database.mongo.service.bd; + +import com.red.circle.other.infra.database.mongo.entity.bd.BdLeaderSalarySettlementRecord; +import com.red.circle.other.infra.enums.BdSettlementStatus; +import com.red.circle.tool.core.date.TimestampUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * BD Leader 结算记录服务实现. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Service +@RequiredArgsConstructor +public class BdLeaderSalarySettlementRecordServiceImpl implements BdLeaderSalarySettlementRecordService { + + private final MongoTemplate mongoTemplate; + + @Override + public void create(BdLeaderSalarySettlementRecord record) { + record.setCreateTime(TimestampUtils.now()); + record.setUpdateTime(TimestampUtils.now()); + mongoTemplate.save(record); + } + + @Override + public void update(BdLeaderSalarySettlementRecord record) { + record.setUpdateTime(TimestampUtils.now()); + mongoTemplate.save(record); + } + + @Override + public BdLeaderSalarySettlementRecord getById(String id) { + return mongoTemplate.findById(id, BdLeaderSalarySettlementRecord.class); + } + + @Override + public BdLeaderSalarySettlementRecord getByBizNo(String bizNo) { + Query query = new Query(Criteria.where("bizNo").is(bizNo)); + return mongoTemplate.findOne(query, BdLeaderSalarySettlementRecord.class); + } + + @Override + public boolean existsByBizNo(String bizNo) { + Query query = new Query(Criteria.where("bizNo").is(bizNo)); + return mongoTemplate.exists(query, BdLeaderSalarySettlementRecord.class); + } + + @Override + public BdLeaderSalarySettlementRecord getByUserIdAndBillBelong(Long bdLeaderUserId, Integer billBelong) { + Query query = new Query(Criteria.where("bdLeaderUserId").is(bdLeaderUserId) + .and("billBelong").is(billBelong)); + return mongoTemplate.findOne(query, BdLeaderSalarySettlementRecord.class); + } + + @Override + public List listByBillBelong(Integer billBelong) { + Query query = new Query(Criteria.where("billBelong").is(billBelong)); + return mongoTemplate.find(query, BdLeaderSalarySettlementRecord.class); + } + + @Override + public void updateStatus(String id, BdSettlementStatus status, String failureReason) { + Query query = new Query(Criteria.where("_id").is(id)); + Update update = new Update() + .set("status", status) + .set("failureReason", failureReason) + .set("updateTime", TimestampUtils.now()); + mongoTemplate.updateFirst(query, update, BdLeaderSalarySettlementRecord.class); + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalaryPolicyService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalaryPolicyService.java new file mode 100644 index 00000000..3917b418 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalaryPolicyService.java @@ -0,0 +1,60 @@ +package com.red.circle.other.infra.database.mongo.service.bd; + +import com.red.circle.other.infra.database.mongo.entity.bd.BdSalaryPolicy; +import com.red.circle.other.infra.enums.BdPolicyType; + +import java.util.List; + +/** + * BD 政策服务. + * + * @author AI Assistant + * @since 2025-10-29 + */ +public interface BdSalaryPolicyService { + + /** + * 创建政策. + */ + void create(BdSalaryPolicy policy); + + /** + * 批量创建政策. + */ + void batchCreate(List policies); + + /** + * 更新政策. + */ + void update(BdSalaryPolicy policy); + + /** + * 根据 ID 查询. + */ + BdSalaryPolicy getById(String id); + + /** + * 查询所有启用的政策. + */ + List listEnabled(); + + /** + * 根据政策类型查询启用的政策(按等级升序). + */ + List listEnabledByType(BdPolicyType policyType); + + /** + * 根据政策类型和等级查询. + */ + BdSalaryPolicy getByTypeAndLevel(BdPolicyType policyType, Integer level); + + /** + * 启用政策. + */ + void enable(String id); + + /** + * 禁用政策. + */ + void disable(String id); +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalaryPolicyServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalaryPolicyServiceImpl.java new file mode 100644 index 00000000..fb66f5bd --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalaryPolicyServiceImpl.java @@ -0,0 +1,95 @@ +package com.red.circle.other.infra.database.mongo.service.bd; + +import com.red.circle.other.infra.database.mongo.entity.bd.BdSalaryPolicy; +import com.red.circle.other.infra.enums.BdPolicyType; +import com.red.circle.tool.core.date.TimestampUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Sort; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * BD 政策服务实现. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Service +@RequiredArgsConstructor +public class BdSalaryPolicyServiceImpl implements BdSalaryPolicyService { + + private final MongoTemplate mongoTemplate; + + @Override + public void create(BdSalaryPolicy policy) { + policy.setCreateTime(TimestampUtils.now()); + policy.setUpdateTime(TimestampUtils.now()); + mongoTemplate.save(policy); + } + + @Override + public void batchCreate(List policies) { + policies.forEach(policy -> { + policy.setCreateTime(TimestampUtils.now()); + policy.setUpdateTime(TimestampUtils.now()); + }); + mongoTemplate.insertAll(policies); + } + + @Override + public void update(BdSalaryPolicy policy) { + policy.setUpdateTime(TimestampUtils.now()); + mongoTemplate.save(policy); + } + + @Override + public BdSalaryPolicy getById(String id) { + return mongoTemplate.findById(id, BdSalaryPolicy.class); + } + + @Override + public List listEnabled() { + Query query = new Query(Criteria.where("enabled").is(true)); + query.with(Sort.by(Sort.Order.asc("policyType"), Sort.Order.asc("level"))); + return mongoTemplate.find(query, BdSalaryPolicy.class); + } + + @Override + public List listEnabledByType(BdPolicyType policyType) { + Query query = new Query(Criteria.where("enabled").is(true) + .and("policyType").is(policyType)); + query.with(Sort.by(Sort.Order.asc("level"))); + return mongoTemplate.find(query, BdSalaryPolicy.class); + } + + @Override + public BdSalaryPolicy getByTypeAndLevel(BdPolicyType policyType, Integer level) { + Query query = new Query(Criteria.where("policyType").is(policyType) + .and("level").is(level) + .and("enabled").is(true)); + return mongoTemplate.findOne(query, BdSalaryPolicy.class); + } + + @Override + public void enable(String id) { + Query query = new Query(Criteria.where("_id").is(id)); + Update update = new Update() + .set("enabled", true) + .set("updateTime", TimestampUtils.now()); + mongoTemplate.updateFirst(query, update, BdSalaryPolicy.class); + } + + @Override + public void disable(String id) { + Query query = new Query(Criteria.where("_id").is(id)); + Update update = new Update() + .set("enabled", false) + .set("updateTime", TimestampUtils.now()); + mongoTemplate.updateFirst(query, update, BdSalaryPolicy.class); + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalarySettlementRecordService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalarySettlementRecordService.java new file mode 100644 index 00000000..0e1486d9 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalarySettlementRecordService.java @@ -0,0 +1,55 @@ +package com.red.circle.other.infra.database.mongo.service.bd; + +import com.red.circle.other.infra.database.mongo.entity.bd.BdSalarySettlementRecord; +import com.red.circle.other.infra.enums.BdSettlementStatus; + +import java.util.List; + +/** + * BD 结算记录服务. + * + * @author AI Assistant + * @since 2025-10-29 + */ +public interface BdSalarySettlementRecordService { + + /** + * 创建结算记录. + */ + void create(BdSalarySettlementRecord record); + + /** + * 更新结算记录. + */ + void update(BdSalarySettlementRecord record); + + /** + * 根据 ID 查询. + */ + BdSalarySettlementRecord getById(String id); + + /** + * 根据业务号查询(幂等性检查). + */ + BdSalarySettlementRecord getByBizNo(String bizNo); + + /** + * 检查业务号是否已存在. + */ + boolean existsByBizNo(String bizNo); + + /** + * 根据 BD 用户 ID 和结算周期查询. + */ + BdSalarySettlementRecord getByUserIdAndBillBelong(Long bdUserId, Integer billBelong); + + /** + * 根据结算周期查询所有记录. + */ + List listByBillBelong(Integer billBelong); + + /** + * 更新结算状态. + */ + void updateStatus(String id, BdSettlementStatus status, String failureReason); +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalarySettlementRecordServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalarySettlementRecordServiceImpl.java new file mode 100644 index 00000000..484ea6b7 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/bd/BdSalarySettlementRecordServiceImpl.java @@ -0,0 +1,79 @@ +package com.red.circle.other.infra.database.mongo.service.bd; + +import com.red.circle.other.infra.database.mongo.entity.bd.BdSalarySettlementRecord; +import com.red.circle.other.infra.enums.BdSettlementStatus; +import com.red.circle.tool.core.date.TimestampUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.data.mongodb.core.MongoTemplate; +import org.springframework.data.mongodb.core.query.Criteria; +import org.springframework.data.mongodb.core.query.Query; +import org.springframework.data.mongodb.core.query.Update; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * BD 结算记录服务实现. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Service +@RequiredArgsConstructor +public class BdSalarySettlementRecordServiceImpl implements BdSalarySettlementRecordService { + + private final MongoTemplate mongoTemplate; + + @Override + public void create(BdSalarySettlementRecord record) { + record.setCreateTime(TimestampUtils.now()); + record.setUpdateTime(TimestampUtils.now()); + mongoTemplate.save(record); + } + + @Override + public void update(BdSalarySettlementRecord record) { + record.setUpdateTime(TimestampUtils.now()); + mongoTemplate.save(record); + } + + @Override + public BdSalarySettlementRecord getById(String id) { + return mongoTemplate.findById(id, BdSalarySettlementRecord.class); + } + + @Override + public BdSalarySettlementRecord getByBizNo(String bizNo) { + Query query = new Query(Criteria.where("bizNo").is(bizNo)); + return mongoTemplate.findOne(query, BdSalarySettlementRecord.class); + } + + @Override + public boolean existsByBizNo(String bizNo) { + Query query = new Query(Criteria.where("bizNo").is(bizNo)); + return mongoTemplate.exists(query, BdSalarySettlementRecord.class); + } + + @Override + public BdSalarySettlementRecord getByUserIdAndBillBelong(Long bdUserId, Integer billBelong) { + Query query = new Query(Criteria.where("bdUserId").is(bdUserId) + .and("billBelong").is(billBelong)); + return mongoTemplate.findOne(query, BdSalarySettlementRecord.class); + } + + @Override + public List listByBillBelong(Integer billBelong) { + Query query = new Query(Criteria.where("billBelong").is(billBelong)); + return mongoTemplate.find(query, BdSalarySettlementRecord.class); + } + + @Override + public void updateStatus(String id, BdSettlementStatus status, String failureReason) { + Query query = new Query(Criteria.where("_id").is(id)); + Update update = new Update() + .set("status", status) + .set("failureReason", failureReason) + .set("updateTime", TimestampUtils.now()); + mongoTemplate.updateFirst(query, update, BdSalarySettlementRecord.class); + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/TeamBillCycleUtils.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/TeamBillCycleUtils.java index b78e00fb..705ca514 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/TeamBillCycleUtils.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/TeamBillCycleUtils.java @@ -561,4 +561,9 @@ public final class TeamBillCycleUtils { } } + public static String parseBillBelongToDateRangeStr(Integer billBelong) { + String[] strings = parseBillBelongToDateRange(billBelong); + return strings[0] + "-" + strings[1]; + } + } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/TeamMemberTargetService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/TeamMemberTargetService.java index 613bbf5a..28e5ce86 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/TeamMemberTargetService.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/TeamMemberTargetService.java @@ -5,6 +5,9 @@ import com.red.circle.other.infra.database.mongo.dto.team.query.TeamMemberTarget import com.red.circle.other.inner.enums.team.TeamMemberTargetIndex; import com.red.circle.other.inner.enums.team.TeamMemberTargetSettlementResult; import com.red.circle.other.inner.enums.team.TeamMemberTargetStatus; +import com.red.circle.other.inner.enums.team.TeamSalaryStatisticsCO; + +import java.math.BigDecimal; import java.util.List; import java.util.Map; import java.util.Set; @@ -192,4 +195,25 @@ public interface TeamMemberTargetService { */ Set repeatTargetHistory(); + /** + * 统计某个团队在某个结算周期的工资总和. + * + * @param teamId 团队ID + * @param billBelong 结算周期 + * @return 工资总和(美元) + * @deprecated 使用 {@link #statisticsTeamSalaryByTeamIdAndBillBelong(Long, Integer)} 代替 + */ + @Deprecated + BigDecimal sumTeamSalaryByTeamIdAndBillBelong(Long teamId, Integer billBelong); + + /** + * 统计某个团队在某个结算周期的工资信息(包含用户列表和工资总和). + * + * @param teamId 团队ID + * @param billBelong 结算周期 + * @return 团队工资统计信息(包含用户ID列表和工资总和) + */ + TeamSalaryStatisticsCO statisticsTeamSalaryByTeamIdAndBillBelong( + Long teamId, 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/TeamMemberTargetServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamMemberTargetServiceImpl.java index 61710ffe..7e45ec5d 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamMemberTargetServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/mongo/service/team/team/impl/TeamMemberTargetServiceImpl.java @@ -13,12 +13,14 @@ import com.red.circle.other.infra.database.mongo.service.team.team.TeamMemberTar import com.red.circle.other.inner.enums.team.TeamMemberTargetIndex; import com.red.circle.other.inner.enums.team.TeamMemberTargetSettlementResult; import com.red.circle.other.inner.enums.team.TeamMemberTargetStatus; +import com.red.circle.other.inner.enums.team.TeamSalaryStatisticsCO; import com.red.circle.tool.core.collection.CollectionUtils; import com.red.circle.tool.core.date.TimestampUtils; import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils; import com.red.circle.tool.core.date.ZonedId; import com.red.circle.tool.core.sequence.IdWorkerUtils; +import java.math.BigDecimal; import java.time.ZonedDateTime; import java.time.temporal.TemporalAdjusters; import java.util.List; @@ -608,5 +610,53 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService { return userIds; } + @Override + public BigDecimal sumTeamSalaryByTeamIdAndBillBelong(Long teamId, Integer billBelong) { + List targets = listByBillBelong(teamId, billBelong); + + if (CollectionUtils.isEmpty(targets)) { + return BigDecimal.ZERO; + } + + return targets.stream() + .filter(target -> target.getSettlementResult() != null + && target.getSettlementResult().getTotalSalary() != null) + .map(target -> target.getSettlementResult().getTotalSalary()) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + @Override + public TeamSalaryStatisticsCO statisticsTeamSalaryByTeamIdAndBillBelong( + Long teamId, Integer billBelong) { + + List targets = listByBillBelong(teamId, billBelong); + + TeamSalaryStatisticsCO statistics = new TeamSalaryStatisticsCO(); + + if (CollectionUtils.isEmpty(targets)) { + statistics.setUserIds(List.of()); + statistics.setTotalSalary(BigDecimal.ZERO); + return statistics; + } + + // 提取所有用户ID + List userIds = targets.stream() + .map(TeamMemberTarget::getUserId) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + + // 统计工资总和 + BigDecimal totalSalary = targets.stream() + .filter(target -> target.getSettlementResult() != null + && target.getSettlementResult().getTotalSalary() != null) + .map(target -> target.getSettlementResult().getTotalSalary()) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + statistics.setUserIds(userIds); + statistics.setTotalSalary(totalSalary); + + return statistics; + } } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/BusinessDevelopmentBaseInfoService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/BusinessDevelopmentBaseInfoService.java index 877741fa..165a8aec 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/BusinessDevelopmentBaseInfoService.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/BusinessDevelopmentBaseInfoService.java @@ -46,4 +46,14 @@ public interface BusinessDevelopmentBaseInfoService extends */ Map mapCountByLeaderUserIds(Set bdLeadUserIds); + /** + * 查询所有 BD Leader. + */ + List listAll(); + + /** + * 根据 BD Leader ID 查询其管理的所有 BD 用户 ID. + */ + List listBdUserIdsByLeaderId(Long bdLeadUserId); + } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/BusinessDevelopmentTeamService.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/BusinessDevelopmentTeamService.java index 111a2c62..2f2a5ebb 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/BusinessDevelopmentTeamService.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/BusinessDevelopmentTeamService.java @@ -94,4 +94,14 @@ public interface BusinessDevelopmentTeamService extends */ void deleteAllByUserId(Long userId); + /** + * 查询所有 BD 团队. + */ + List listAll(); + + /** + * 根据 BD 用户 ID 查询其管理的所有团队. + */ + List listByBdUserId(Long bdUserId); + } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/impl/BusinessDevelopmentBaseInfoServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/impl/BusinessDevelopmentBaseInfoServiceImpl.java index b3c43a0b..b805f1c2 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/impl/BusinessDevelopmentBaseInfoServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/impl/BusinessDevelopmentBaseInfoServiceImpl.java @@ -179,4 +179,25 @@ public class BusinessDevelopmentBaseInfoServiceImpl extends .groupingBy(BusinessDevelopmentBaseInfo::getBdLeadUserId, Collectors.counting())); } + @Override + public List listAll() { + return list(); + } + + @Override + public List listBdUserIdsByLeaderId(Long bdLeadUserId) { + List bdList = query() + .select(BusinessDevelopmentBaseInfo::getUserId) + .eq(BusinessDevelopmentBaseInfo::getBdLeadUserId, bdLeadUserId) + .list(); + + if (CollectionUtils.isEmpty(bdList)) { + return java.util.Collections.emptyList(); + } + + return bdList.stream() + .map(BusinessDevelopmentBaseInfo::getUserId) + .collect(Collectors.toList()); + } + } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/impl/BusinessDevelopmentTeamServiceImpl.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/impl/BusinessDevelopmentTeamServiceImpl.java index 4bc5e503..75004275 100644 --- a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/impl/BusinessDevelopmentTeamServiceImpl.java +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/database/rds/service/team/impl/BusinessDevelopmentTeamServiceImpl.java @@ -188,4 +188,16 @@ public class BusinessDevelopmentTeamServiceImpl extends .execute(); } + @Override + public List listAll() { + return list(); + } + + @Override + public List listByBdUserId(Long bdUserId) { + return query() + .eq(BusinessDevelopmentTeam::getUserId, bdUserId) + .list(); + } + } diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/enums/BdPolicyType.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/enums/BdPolicyType.java new file mode 100644 index 00000000..9820a8a6 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/enums/BdPolicyType.java @@ -0,0 +1,44 @@ +package com.red.circle.other.infra.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * BD 政策类型枚举. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Getter +@AllArgsConstructor +public enum BdPolicyType { + + /** + * 团队工资政策. + */ + TEAM_SALARY("团队工资政策"), + + /** + * 团队充值政策. + */ + TEAM_RECHARGE("团队充值政策"); + + /** + * 描述. + */ + private final String description; + + /** + * 判断是否为团队工资政策. + */ + public boolean isTeamSalary() { + return this == TEAM_SALARY; + } + + /** + * 判断是否为团队充值政策. + */ + public boolean isTeamRecharge() { + return this == TEAM_RECHARGE; + } +} diff --git a/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/enums/BdSettlementStatus.java b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/enums/BdSettlementStatus.java new file mode 100644 index 00000000..c7fd1e84 --- /dev/null +++ b/rc-service/rc-service-other/other-infrastructure/src/main/java/com/red/circle/other/infra/enums/BdSettlementStatus.java @@ -0,0 +1,56 @@ +package com.red.circle.other.infra.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * BD 结算状态枚举. + * + * @author AI Assistant + * @since 2025-10-29 + */ +@Getter +@AllArgsConstructor +public enum BdSettlementStatus { + + /** + * 待结算. + */ + PENDING("待结算"), + + /** + * 结算成功. + */ + SUCCESS("结算成功"), + + /** + * 结算失败. + */ + FAILED("结算失败"); + + /** + * 描述. + */ + private final String description; + + /** + * 判断是否成功. + */ + public boolean isSuccess() { + return this == SUCCESS; + } + + /** + * 判断是否失败. + */ + public boolean isFailed() { + return this == FAILED; + } + + /** + * 判断是否待处理. + */ + public boolean isPending() { + return this == PENDING; + } +} diff --git a/rc-service/rc-service-other/other-start/src/test/java/ApiTest.java b/rc-service/rc-service-other/other-start/src/test/java/ApiTest.java index 89f2386e..8e14cd0d 100644 --- a/rc-service/rc-service-other/other-start/src/test/java/ApiTest.java +++ b/rc-service/rc-service-other/other-start/src/test/java/ApiTest.java @@ -1,3 +1,4 @@ +import com.red.circle.other.infra.database.mongo.service.team.TeamBillCycleUtils; import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils; import com.red.circle.tool.crypto.SecurityUtils; import org.junit.Test; @@ -7,7 +8,9 @@ public class ApiTest { @Test public void test() { - System.out.println(new BCryptPasswordEncoder().encode("8826681")); + String[] strings = TeamBillCycleUtils.parseBillBelongToDateRange(20251001); + System.out.println(strings[0]); + System.out.println(strings[1]); } @Test