新增bd bdleader 工资结算
This commit is contained in:
parent
068a58cf00
commit
dd6314cd74
@ -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<Map<String, Object>> statistics(
|
||||
@RequestBody @Validated InAppPurchaseDetailsQryCmd qryCmd);
|
||||
|
||||
/**
|
||||
* 批量查询用户充值统计.
|
||||
* 统计指定用户列表在指定时间范围内的支付成功订单金额总和.
|
||||
*
|
||||
* @param userIds 用户ID列表
|
||||
* @param sysOrigin 系统来源
|
||||
* @param startTime 开始时间(可选)
|
||||
* @param endTime 结束时间(可选)
|
||||
* @return 用户充值统计列表(包含用户ID、总金额、订单数量)
|
||||
*/
|
||||
@PostMapping("/batchUserRechargeStatistics")
|
||||
ResultResponse<List<BatchUserRechargeStatisticsDTO>> batchUserRechargeStatistics(
|
||||
@RequestBody List<Long> userIds,
|
||||
@RequestParam("sysOrigin") String sysOrigin,
|
||||
@RequestParam(value = "startTime", required = false) LocalDateTime startTime,
|
||||
@RequestParam(value = "endTime", required = false) LocalDateTime endTime);
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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<Long> userIds;
|
||||
|
||||
/**
|
||||
* 工资总金额(美元).
|
||||
*/
|
||||
private BigDecimal totalSalary;
|
||||
}
|
||||
@ -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;
|
||||
|
||||
@ -88,4 +88,19 @@ public interface InAppPurchaseDetailsService {
|
||||
List<InAppPurchaseDetails> listAll();
|
||||
|
||||
void updateMetadata(String id, Map<String, String> metadata);
|
||||
|
||||
/**
|
||||
* 批量统计用户充值.
|
||||
*
|
||||
* @param userIds 用户ID列表
|
||||
* @param sysOrigin 系统来源
|
||||
* @param startTime 开始时间(可选)
|
||||
* @param endTime 结束时间(可选)
|
||||
* @return 用户充值统计列表(按用户ID分组)
|
||||
*/
|
||||
List<CountUserInAppPurchase> batchUserRechargeStatistics(
|
||||
List<Long> userIds,
|
||||
String sysOrigin,
|
||||
LocalDateTime startTime,
|
||||
LocalDateTime endTime);
|
||||
}
|
||||
|
||||
@ -26,4 +26,9 @@ public class CountUserInAppPurchase implements Serializable {
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private BigDecimal amount;
|
||||
|
||||
/**
|
||||
* 订单数量.
|
||||
*/
|
||||
private Integer orderCount;
|
||||
|
||||
}
|
||||
|
||||
@ -348,4 +348,57 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ
|
||||
InAppPurchaseDetails.class
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<CountUserInAppPurchase> batchUserRechargeStatistics(
|
||||
List<Long> 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<BasicDBObject> 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<BasicDBObject> 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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<InAppPurchaseDetailsDTO> toListInAppPurchaseDetailsDTO(List<InAppPurchaseDetails> details);
|
||||
|
||||
List<CountUserInAppPurchaseDTO> toListCountUserInAppPurchase(List<CountUserInAppPurchase> list);
|
||||
|
||||
/**
|
||||
* 转换为批量用户充值统计DTO列表.
|
||||
*
|
||||
* @param list CountUserInAppPurchase列表
|
||||
* @return BatchUserRechargeStatisticsDTO列表
|
||||
*/
|
||||
List<BatchUserRechargeStatisticsDTO> toBatchUserRechargeStatisticsDTOList(List<CountUserInAppPurchase> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<List<BatchUserRechargeStatisticsDTO>> batchUserRechargeStatistics(
|
||||
List<Long> userIds,
|
||||
String sysOrigin,
|
||||
LocalDateTime startTime,
|
||||
LocalDateTime endTime) {
|
||||
return ResultResponse.success(
|
||||
inAppPurchaseDetailsClientService.batchUserRechargeStatistics(
|
||||
userIds, sysOrigin, startTime, endTime)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -31,4 +31,19 @@ public interface InAppPurchaseDetailsClientService {
|
||||
|
||||
List<CountUserInAppPurchaseDTO> countUserInAppPurchase(String sysOrigin, LocalDateTime startTime, LocalDateTime endTime, Integer limit);
|
||||
|
||||
/**
|
||||
* 批量查询用户充值统计.
|
||||
*
|
||||
* @param userIds 用户ID列表
|
||||
* @param sysOrigin 系统来源
|
||||
* @param startTime 开始时间(可选)
|
||||
* @param endTime 结束时间(可选)
|
||||
* @return 用户充值统计列表
|
||||
*/
|
||||
List<com.red.circle.order.inner.model.dto.inapp.BatchUserRechargeStatisticsDTO> batchUserRechargeStatistics(
|
||||
List<Long> userIds,
|
||||
String sysOrigin,
|
||||
LocalDateTime startTime,
|
||||
LocalDateTime endTime);
|
||||
|
||||
}
|
||||
|
||||
@ -55,4 +55,19 @@ public class InAppPurchaseDetailsClientServiceImpl implements InAppPurchaseDetai
|
||||
return inAppPurchaseDetailsInnerConvert.toListCountUserInAppPurchase(countUserInAppPurchases);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<com.red.circle.order.inner.model.dto.inapp.BatchUserRechargeStatisticsDTO> batchUserRechargeStatistics(
|
||||
List<Long> userIds,
|
||||
String sysOrigin,
|
||||
LocalDateTime startTime,
|
||||
LocalDateTime endTime) {
|
||||
|
||||
List<CountUserInAppPurchase> countUserInAppPurchases =
|
||||
inAppPurchaseDetailsService.batchUserRechargeStatistics(
|
||||
userIds, sysOrigin, startTime, endTime);
|
||||
|
||||
return inAppPurchaseDetailsInnerConvert.toBatchUserRechargeStatisticsDTOList(
|
||||
countUserInAppPurchases);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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<BusinessDevelopmentBaseInfo> allBdLeaders = businessDevelopmentBaseInfoService.listAll();
|
||||
|
||||
if (CollectionUtils.isEmpty(allBdLeaders)) {
|
||||
log.warn("没有找到任何 BD Leader 数据");
|
||||
return;
|
||||
}
|
||||
|
||||
// 按 BD Leader 用户 ID 分组(去重)
|
||||
Map<Long, List<BusinessDevelopmentBaseInfo>> 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<Long> 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<BdLeaderSalaryPolicy> 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<Long> 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;
|
||||
}
|
||||
}
|
||||
@ -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<BusinessDevelopmentTeam> allBdTeams = businessDevelopmentTeamService.listAll();
|
||||
|
||||
if (CollectionUtils.isEmpty(allBdTeams)) {
|
||||
log.warn("没有找到任何 BD 数据");
|
||||
return;
|
||||
}
|
||||
|
||||
// 按 BD 用户 ID 分组(去重)
|
||||
Map<Long, List<BusinessDevelopmentTeam>> 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<BusinessDevelopmentTeam> 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<Long> 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<Long> 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<List<BatchUserRechargeStatisticsDTO>> 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<BdSalaryPolicy> salaryPolicies = bdSalaryPolicyService.listEnabledByType(BdPolicyType.TEAM_SALARY);
|
||||
List<BdSalaryPolicy> 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<BdSalaryPolicy> 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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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<BdLeaderSalaryPolicy> policies);
|
||||
|
||||
/**
|
||||
* 更新政策.
|
||||
*/
|
||||
void update(BdLeaderSalaryPolicy policy);
|
||||
|
||||
/**
|
||||
* 根据 ID 查询.
|
||||
*/
|
||||
BdLeaderSalaryPolicy getById(String id);
|
||||
|
||||
/**
|
||||
* 查询所有启用的政策(按等级升序).
|
||||
*/
|
||||
List<BdLeaderSalaryPolicy> listEnabled();
|
||||
|
||||
/**
|
||||
* 根据等级查询.
|
||||
*/
|
||||
BdLeaderSalaryPolicy getByLevel(Integer level);
|
||||
|
||||
/**
|
||||
* 启用政策.
|
||||
*/
|
||||
void enable(String id);
|
||||
|
||||
/**
|
||||
* 禁用政策.
|
||||
*/
|
||||
void disable(String id);
|
||||
}
|
||||
@ -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<BdLeaderSalaryPolicy> 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<BdLeaderSalaryPolicy> 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);
|
||||
}
|
||||
}
|
||||
@ -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<BdLeaderSalarySettlementRecord> listByBillBelong(Integer billBelong);
|
||||
|
||||
/**
|
||||
* 更新结算状态.
|
||||
*/
|
||||
void updateStatus(String id, BdSettlementStatus status, String failureReason);
|
||||
}
|
||||
@ -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<BdLeaderSalarySettlementRecord> 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);
|
||||
}
|
||||
}
|
||||
@ -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<BdSalaryPolicy> policies);
|
||||
|
||||
/**
|
||||
* 更新政策.
|
||||
*/
|
||||
void update(BdSalaryPolicy policy);
|
||||
|
||||
/**
|
||||
* 根据 ID 查询.
|
||||
*/
|
||||
BdSalaryPolicy getById(String id);
|
||||
|
||||
/**
|
||||
* 查询所有启用的政策.
|
||||
*/
|
||||
List<BdSalaryPolicy> listEnabled();
|
||||
|
||||
/**
|
||||
* 根据政策类型查询启用的政策(按等级升序).
|
||||
*/
|
||||
List<BdSalaryPolicy> listEnabledByType(BdPolicyType policyType);
|
||||
|
||||
/**
|
||||
* 根据政策类型和等级查询.
|
||||
*/
|
||||
BdSalaryPolicy getByTypeAndLevel(BdPolicyType policyType, Integer level);
|
||||
|
||||
/**
|
||||
* 启用政策.
|
||||
*/
|
||||
void enable(String id);
|
||||
|
||||
/**
|
||||
* 禁用政策.
|
||||
*/
|
||||
void disable(String id);
|
||||
}
|
||||
@ -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<BdSalaryPolicy> 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<BdSalaryPolicy> 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<BdSalaryPolicy> 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);
|
||||
}
|
||||
}
|
||||
@ -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<BdSalarySettlementRecord> listByBillBelong(Integer billBelong);
|
||||
|
||||
/**
|
||||
* 更新结算状态.
|
||||
*/
|
||||
void updateStatus(String id, BdSettlementStatus status, String failureReason);
|
||||
}
|
||||
@ -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<BdSalarySettlementRecord> 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);
|
||||
}
|
||||
}
|
||||
@ -561,4 +561,9 @@ public final class TeamBillCycleUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static String parseBillBelongToDateRangeStr(Integer billBelong) {
|
||||
String[] strings = parseBillBelongToDateRange(billBelong);
|
||||
return strings[0] + "-" + strings[1];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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<String> 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);
|
||||
|
||||
}
|
||||
|
||||
@ -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<TeamMemberTarget> 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<TeamMemberTarget> targets = listByBillBelong(teamId, billBelong);
|
||||
|
||||
TeamSalaryStatisticsCO statistics = new TeamSalaryStatisticsCO();
|
||||
|
||||
if (CollectionUtils.isEmpty(targets)) {
|
||||
statistics.setUserIds(List.of());
|
||||
statistics.setTotalSalary(BigDecimal.ZERO);
|
||||
return statistics;
|
||||
}
|
||||
|
||||
// 提取所有用户ID
|
||||
List<Long> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -46,4 +46,14 @@ public interface BusinessDevelopmentBaseInfoService extends
|
||||
*/
|
||||
Map<Long, Long> mapCountByLeaderUserIds(Set<Long> bdLeadUserIds);
|
||||
|
||||
/**
|
||||
* 查询所有 BD Leader.
|
||||
*/
|
||||
List<BusinessDevelopmentBaseInfo> listAll();
|
||||
|
||||
/**
|
||||
* 根据 BD Leader ID 查询其管理的所有 BD 用户 ID.
|
||||
*/
|
||||
List<Long> listBdUserIdsByLeaderId(Long bdLeadUserId);
|
||||
|
||||
}
|
||||
|
||||
@ -94,4 +94,14 @@ public interface BusinessDevelopmentTeamService extends
|
||||
*/
|
||||
void deleteAllByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 查询所有 BD 团队.
|
||||
*/
|
||||
List<BusinessDevelopmentTeam> listAll();
|
||||
|
||||
/**
|
||||
* 根据 BD 用户 ID 查询其管理的所有团队.
|
||||
*/
|
||||
List<BusinessDevelopmentTeam> listByBdUserId(Long bdUserId);
|
||||
|
||||
}
|
||||
|
||||
@ -179,4 +179,25 @@ public class BusinessDevelopmentBaseInfoServiceImpl extends
|
||||
.groupingBy(BusinessDevelopmentBaseInfo::getBdLeadUserId, Collectors.counting()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BusinessDevelopmentBaseInfo> listAll() {
|
||||
return list();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Long> listBdUserIdsByLeaderId(Long bdLeadUserId) {
|
||||
List<BusinessDevelopmentBaseInfo> 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -188,4 +188,16 @@ public class BusinessDevelopmentTeamServiceImpl extends
|
||||
.execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BusinessDevelopmentTeam> listAll() {
|
||||
return list();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<BusinessDevelopmentTeam> listByBdUserId(Long bdUserId) {
|
||||
return query()
|
||||
.eq(BusinessDevelopmentTeam::getUserId, bdUserId)
|
||||
.list();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user