新增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.framework.dto.ResultResponse;
|
||||||
import com.red.circle.order.inner.model.cmd.inapp.InAppPurchaseDetailsQryCmd;
|
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.CountUserInAppPurchaseDTO;
|
||||||
import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO;
|
import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@ -51,4 +52,21 @@ public interface InAppPurchaseDetailsClientApi {
|
|||||||
@PostMapping("/statistics")
|
@PostMapping("/statistics")
|
||||||
ResultResponse<Map<String, Object>> statistics(
|
ResultResponse<Map<String, Object>> statistics(
|
||||||
@RequestBody @Validated InAppPurchaseDetailsQryCmd qryCmd);
|
@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"),
|
NEW_USER_RED_PACKET_WITHDRAW("New user red packet withdraw"),
|
||||||
|
|
||||||
|
BD_SALARY_SETTLEMENT("BD工资结算"),
|
||||||
|
|
||||||
|
BD_LEADER_SALARY_SETTLEMENT("BD Leader工资结算"),
|
||||||
;
|
;
|
||||||
|
|
||||||
private final String describe;
|
private final String describe;
|
||||||
|
|||||||
@ -88,4 +88,19 @@ public interface InAppPurchaseDetailsService {
|
|||||||
List<InAppPurchaseDetails> listAll();
|
List<InAppPurchaseDetails> listAll();
|
||||||
|
|
||||||
void updateMetadata(String id, Map<String, String> metadata);
|
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)
|
@JsonSerialize(using = ToStringSerializer.class)
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单数量.
|
||||||
|
*/
|
||||||
|
private Integer orderCount;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -348,4 +348,57 @@ public class InAppPurchaseDetailsServiceImpl implements InAppPurchaseDetailsServ
|
|||||||
InAppPurchaseDetails.class
|
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.entity.assist.CountUserInAppPurchase;
|
||||||
import com.red.circle.order.infra.database.mongo.order.query.InAppPurchaseDetailsQuery;
|
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.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.CountUserInAppPurchaseDTO;
|
||||||
import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO;
|
import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -23,4 +24,29 @@ public interface InAppPurchaseDetailsInnerConvert {
|
|||||||
List<InAppPurchaseDetailsDTO> toListInAppPurchaseDetailsDTO(List<InAppPurchaseDetails> details);
|
List<InAppPurchaseDetailsDTO> toListInAppPurchaseDetailsDTO(List<InAppPurchaseDetails> details);
|
||||||
|
|
||||||
List<CountUserInAppPurchaseDTO> toListCountUserInAppPurchase(List<CountUserInAppPurchase> list);
|
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.framework.dto.ResultResponse;
|
||||||
import com.red.circle.order.inner.endpoint.api.InAppPurchaseDetailsClientApi;
|
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.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.CountUserInAppPurchaseDTO;
|
||||||
import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO;
|
import com.red.circle.order.inner.model.dto.inapp.InAppPurchaseDetailsDTO;
|
||||||
import com.red.circle.order.inner.service.InAppPurchaseDetailsClientService;
|
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);
|
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);
|
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.TeamMemberTargetIndex;
|
||||||
import com.red.circle.other.inner.enums.team.TeamMemberTargetSettlementResult;
|
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.TeamMemberTargetStatus;
|
||||||
|
import com.red.circle.other.inner.enums.team.TeamSalaryStatisticsCO;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -192,4 +195,25 @@ public interface TeamMemberTargetService {
|
|||||||
*/
|
*/
|
||||||
Set<String> repeatTargetHistory();
|
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.TeamMemberTargetIndex;
|
||||||
import com.red.circle.other.inner.enums.team.TeamMemberTargetSettlementResult;
|
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.TeamMemberTargetStatus;
|
||||||
|
import com.red.circle.other.inner.enums.team.TeamSalaryStatisticsCO;
|
||||||
import com.red.circle.tool.core.collection.CollectionUtils;
|
import com.red.circle.tool.core.collection.CollectionUtils;
|
||||||
import com.red.circle.tool.core.date.TimestampUtils;
|
import com.red.circle.tool.core.date.TimestampUtils;
|
||||||
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
|
import com.red.circle.tool.core.date.ZonedDateTimeAsiaRiyadhUtils;
|
||||||
import com.red.circle.tool.core.date.ZonedId;
|
import com.red.circle.tool.core.date.ZonedId;
|
||||||
import com.red.circle.tool.core.sequence.IdWorkerUtils;
|
import com.red.circle.tool.core.sequence.IdWorkerUtils;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.time.ZonedDateTime;
|
import java.time.ZonedDateTime;
|
||||||
import java.time.temporal.TemporalAdjusters;
|
import java.time.temporal.TemporalAdjusters;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -608,5 +610,53 @@ public class TeamMemberTargetServiceImpl implements TeamMemberTargetService {
|
|||||||
return userIds;
|
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);
|
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);
|
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()));
|
.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();
|
.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.core.date.ZonedDateTimeAsiaRiyadhUtils;
|
||||||
import com.red.circle.tool.crypto.SecurityUtils;
|
import com.red.circle.tool.crypto.SecurityUtils;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -7,7 +8,9 @@ public class ApiTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void 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
|
@Test
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user