Compare commits
2 Commits
98e427c65a
...
be54601cdd
| Author | SHA1 | Date | |
|---|---|---|---|
| be54601cdd | |||
| 2207add193 |
@@ -16,7 +16,7 @@ import static com.yolo.keyboard.framework.common.util.date.DateUtils.FORMAT_YEAR
|
||||
public class TenantBalancePageReqVO extends PageParam {
|
||||
|
||||
@Schema(description = "当前积分余额")
|
||||
private Integer balance;
|
||||
private BigDecimal balance;
|
||||
|
||||
@Schema(description = "乐观锁版本号")
|
||||
private Integer version;
|
||||
|
||||
@@ -20,7 +20,7 @@ public class TenantBalanceRespVO {
|
||||
|
||||
@Schema(description = "当前积分余额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("当前积分余额")
|
||||
private Integer balance;
|
||||
private BigDecimal balance;
|
||||
|
||||
@Schema(description = "乐观锁版本号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@ExcelProperty("乐观锁版本号")
|
||||
|
||||
@@ -18,7 +18,7 @@ public class TenantBalanceSaveReqVO {
|
||||
|
||||
@Schema(description = "当前积分余额", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "当前积分余额不能为空")
|
||||
private Integer balance;
|
||||
private BigDecimal balance;
|
||||
|
||||
@Schema(description = "乐观锁版本号", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "乐观锁版本号不能为空")
|
||||
|
||||
@@ -38,10 +38,12 @@ public class KeyboardTenantCommissionController {
|
||||
@Resource
|
||||
private KeyboardTenantCommissionService tenantCommissionService;
|
||||
|
||||
|
||||
@PostMapping("/create")
|
||||
@Operation(summary = "创建租户内购分成记录")
|
||||
@PreAuthorize("@ss.hasPermission('keyboard:tenant-commission:create')")
|
||||
public CommonResult<Long> createTenantCommission(@Valid @RequestBody KeyboardTenantCommissionSaveReqVO createReqVO) {
|
||||
|
||||
return success(tenantCommissionService.createTenantCommission(createReqVO));
|
||||
}
|
||||
|
||||
@@ -88,6 +90,14 @@ public class KeyboardTenantCommissionController {
|
||||
return success(BeanUtils.toBean(pageResult, KeyboardTenantCommissionRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/my-page")
|
||||
@Operation(summary = "获得当前登录租户的分成记录分页")
|
||||
@PreAuthorize("@ss.hasPermission('keyboard:tenant-commission:query:mypage')")
|
||||
public CommonResult<PageResult<KeyboardTenantCommissionRespVO>> getMyTenantCommissionPage(@Valid KeyboardTenantCommissionPageReqVO pageReqVO) {
|
||||
PageResult<KeyboardTenantCommissionDO> pageResult = tenantCommissionService.getMyTenantCommissionPage(pageReqVO);
|
||||
return success(BeanUtils.toBean(pageResult, KeyboardTenantCommissionRespVO.class));
|
||||
}
|
||||
|
||||
@GetMapping("/export-excel")
|
||||
@Operation(summary = "导出租户内购分成记录 Excel")
|
||||
@PreAuthorize("@ss.hasPermission('keyboard:tenant-commission:export')")
|
||||
|
||||
@@ -29,6 +29,16 @@ public interface KeyboardTenantCommissionMapper extends BaseMapperX<KeyboardTena
|
||||
return selectOne(KeyboardTenantCommissionDO::getPurchaseRecordId, purchaseRecordId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据内购记录ID和租户ID查询分成记录
|
||||
* 用于检查某个租户是否已经对某条内购记录计算过分成
|
||||
*/
|
||||
default KeyboardTenantCommissionDO selectByPurchaseRecordIdAndTenantId(Integer purchaseRecordId, Long tenantId) {
|
||||
return selectOne(new LambdaQueryWrapperX<KeyboardTenantCommissionDO>()
|
||||
.eq(KeyboardTenantCommissionDO::getPurchaseRecordId, purchaseRecordId)
|
||||
.eq(KeyboardTenantCommissionDO::getTenantId, tenantId));
|
||||
}
|
||||
|
||||
default PageResult<KeyboardTenantCommissionDO> selectPage(KeyboardTenantCommissionPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<KeyboardTenantCommissionDO>()
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getPurchaseRecordId, reqVO.getPurchaseRecordId())
|
||||
@@ -48,4 +58,27 @@ public interface KeyboardTenantCommissionMapper extends BaseMapperX<KeyboardTena
|
||||
.orderByDesc(KeyboardTenantCommissionDO::getId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据租户ID分页查询分成记录
|
||||
*/
|
||||
default PageResult<KeyboardTenantCommissionDO> selectPageByTenantId(KeyboardTenantCommissionPageReqVO reqVO, Long tenantId) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<KeyboardTenantCommissionDO>()
|
||||
.eq(KeyboardTenantCommissionDO::getTenantId, tenantId)
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getPurchaseRecordId, reqVO.getPurchaseRecordId())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getTransactionId, reqVO.getTransactionId())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getInviteeUserId, reqVO.getInviteeUserId())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getInviterUserId, reqVO.getInviterUserId())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getPurchaseAmount, reqVO.getPurchaseAmount())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getCommissionRate, reqVO.getCommissionRate())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getCommissionAmount, reqVO.getCommissionAmount())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getStatus, reqVO.getStatus())
|
||||
.betweenIfPresent(KeyboardTenantCommissionDO::getPurchaseTime, reqVO.getPurchaseTime())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getSettledAt, reqVO.getSettledAt())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getBalanceTransactionId, reqVO.getBalanceTransactionId())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getCreatedAt, reqVO.getCreatedAt())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getUpdatedAt, reqVO.getUpdatedAt())
|
||||
.eqIfPresent(KeyboardTenantCommissionDO::getRemark, reqVO.getRemark())
|
||||
.orderByDesc(KeyboardTenantCommissionDO::getId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import java.util.List;
|
||||
/**
|
||||
* 租户分成计算定时任务
|
||||
* 每小时执行一次,计算邀请用户的内购分成
|
||||
* 支持一级代理和二级代理的分成计算
|
||||
*
|
||||
* @author ziin
|
||||
*/
|
||||
@@ -110,101 +111,112 @@ public class TenantCommissionCalculateJob implements JobHandler {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取收益归属租户
|
||||
Long tenantId = invite.getProfitTenantId();
|
||||
if (tenantId == null) {
|
||||
tenantId = invite.getInviterTenantId();
|
||||
// 获取收益归属租户(邀请人所属租户)
|
||||
Long inviterTenantId = invite.getProfitTenantId();
|
||||
if (inviterTenantId == null) {
|
||||
inviterTenantId = invite.getInviterTenantId();
|
||||
}
|
||||
if (tenantId == null) {
|
||||
if (inviterTenantId == null) {
|
||||
log.warn("[TenantCommissionCalculateJob] 用户 {} 的邀请关系没有关联租户,跳过", record.getUserId());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取租户信息和分成比例
|
||||
TenantDO tenant = tenantMapper.selectById(tenantId);
|
||||
if (tenant == null) {
|
||||
log.warn("[TenantCommissionCalculateJob] 租户 {} 不存在,跳过", tenantId);
|
||||
// 获取邀请人租户信息
|
||||
TenantDO inviterTenant = tenantMapper.selectById(inviterTenantId);
|
||||
if (inviterTenant == null) {
|
||||
log.warn("[TenantCommissionCalculateJob] 租户 {} 不存在,跳过", inviterTenantId);
|
||||
continue;
|
||||
}
|
||||
|
||||
BigDecimal commissionRate = tenant.getProfitShareRatio();
|
||||
if (commissionRate == null || commissionRate.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
log.debug("[TenantCommissionCalculateJob] 租户 {} 没有设置分成比例,跳过", tenantId);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3. 计算分成金额
|
||||
// 获取内购金额
|
||||
BigDecimal purchaseAmount = record.getPrice();
|
||||
if (purchaseAmount == null || purchaseAmount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
log.debug("[TenantCommissionCalculateJob] 内购记录 {} 金额无效,跳过", record.getId());
|
||||
continue;
|
||||
}
|
||||
|
||||
BigDecimal commissionAmount = purchaseAmount.multiply(commissionRate)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
// 4. 创建分成记录
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
LocalDateTime withdrawableAt = now.plusDays(30); // 30天后可提现
|
||||
KeyboardTenantCommissionDO commission = KeyboardTenantCommissionDO.builder()
|
||||
.purchaseRecordId(record.getId())
|
||||
.transactionId(record.getTransactionId())
|
||||
.inviteeUserId(record.getUserId())
|
||||
.inviterUserId(invite.getProfitEmployeeId())
|
||||
.tenantId(tenantId)
|
||||
.purchaseAmount(purchaseAmount)
|
||||
.commissionRate(commissionRate)
|
||||
.commissionAmount(commissionAmount)
|
||||
.status("SETTLED")
|
||||
.purchaseTime(record.getPurchaseTime())
|
||||
.settledAt(now)
|
||||
.withdrawableAt(withdrawableAt)
|
||||
.withdrawableProcessed(false)
|
||||
.createdAt(now)
|
||||
.updatedAt(now)
|
||||
.build();
|
||||
LocalDateTime withdrawableAt = now.plusDays(30);
|
||||
|
||||
// 5. 更新租户余额(分成金额先计入总余额,30天后才可提现)
|
||||
TenantBalanceDO balance = tenantBalanceMapper.selectById(tenantId);
|
||||
if (balance == null) {
|
||||
// 如果租户余额记录不存在,创建一个
|
||||
balance = new TenantBalanceDO();
|
||||
balance.setId(tenantId);
|
||||
balance.setBalance(commissionAmount);
|
||||
balance.setWithdrawableBalance(BigDecimal.ZERO);
|
||||
balance.setFrozenAmt(BigDecimal.ZERO);
|
||||
balance.setVersion(0);
|
||||
tenantBalanceMapper.insert(balance);
|
||||
// 判断是否是二级代理(有上级租户)
|
||||
if (inviterTenant.getParentId() != null) {
|
||||
// 二级代理场景:需要给一级代理和二级代理都分成
|
||||
TenantDO parentTenant = tenantMapper.selectById(inviterTenant.getParentId());
|
||||
if (parentTenant == null) {
|
||||
log.warn("[TenantCommissionCalculateJob] 上级租户 {} 不存在,跳过", inviterTenant.getParentId());
|
||||
continue;
|
||||
}
|
||||
|
||||
// 使用一级代理的分成比例计算总分成
|
||||
BigDecimal totalCommissionRate = parentTenant.getProfitShareRatio();
|
||||
if (totalCommissionRate == null || totalCommissionRate.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
log.debug("[TenantCommissionCalculateJob] 一级代理租户 {} 没有设置分成比例,跳过", parentTenant.getId());
|
||||
continue;
|
||||
}
|
||||
|
||||
BigDecimal totalCommissionAmount = purchaseAmount.multiply(totalCommissionRate)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
// 获取二级代理的返点比例
|
||||
BigDecimal rebateRatio = inviterTenant.getUpstreamRebateRatio();
|
||||
if (rebateRatio == null || rebateRatio.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
// 如果没有设置返点比例,全部归一级代理
|
||||
log.debug("[TenantCommissionCalculateJob] 二级代理租户 {} 没有设置返点比例,全部归一级代理", inviterTenantId);
|
||||
int count = createCommissionRecord(record, invite, parentTenant.getId(), purchaseAmount,
|
||||
totalCommissionRate, totalCommissionAmount, now, withdrawableAt,
|
||||
"一级代理分成(二级代理无返点)");
|
||||
commissionCount += count;
|
||||
totalCommission = totalCommission.add(totalCommissionAmount);
|
||||
} else {
|
||||
// 计算二级代理分成
|
||||
BigDecimal secondLevelAmount = totalCommissionAmount.multiply(rebateRatio)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
// 计算一级代理分成
|
||||
BigDecimal firstLevelAmount = totalCommissionAmount.subtract(secondLevelAmount);
|
||||
|
||||
// 为二级代理创建分成记录
|
||||
if (secondLevelAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
int count = createCommissionRecord(record, invite, inviterTenantId, purchaseAmount,
|
||||
rebateRatio, secondLevelAmount, now, withdrawableAt,
|
||||
"二级代理分成");
|
||||
commissionCount += count;
|
||||
totalCommission = totalCommission.add(secondLevelAmount);
|
||||
log.info("[TenantCommissionCalculateJob] 内购记录 {}, 二级代理 {}, 分成金额 {}",
|
||||
record.getId(), inviterTenantId, secondLevelAmount);
|
||||
}
|
||||
|
||||
// 为一级代理创建分成记录
|
||||
if (firstLevelAmount.compareTo(BigDecimal.ZERO) > 0) {
|
||||
BigDecimal firstLevelRate = BigDecimal.ONE.subtract(rebateRatio);
|
||||
int count = createCommissionRecord(record, invite, parentTenant.getId(), purchaseAmount,
|
||||
firstLevelRate, firstLevelAmount, now, withdrawableAt,
|
||||
"一级代理分成(扣除二级返点)");
|
||||
commissionCount += count;
|
||||
totalCommission = totalCommission.add(firstLevelAmount);
|
||||
log.info("[TenantCommissionCalculateJob] 内购记录 {}, 一级代理 {}, 分成金额 {}",
|
||||
record.getId(), parentTenant.getId(), firstLevelAmount);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
BigDecimal newBalance = balance.getBalance().add(commissionAmount);
|
||||
balance.setBalance(newBalance);
|
||||
tenantBalanceMapper.updateById(balance);
|
||||
// 一级代理场景:全部分成归一级代理
|
||||
BigDecimal commissionRate = inviterTenant.getProfitShareRatio();
|
||||
if (commissionRate == null || commissionRate.compareTo(BigDecimal.ZERO) <= 0) {
|
||||
log.debug("[TenantCommissionCalculateJob] 租户 {} 没有设置分成比例,跳过", inviterTenantId);
|
||||
continue;
|
||||
}
|
||||
|
||||
BigDecimal commissionAmount = purchaseAmount.multiply(commissionRate)
|
||||
.setScale(2, RoundingMode.HALF_UP);
|
||||
|
||||
int count = createCommissionRecord(record, invite, inviterTenantId, purchaseAmount,
|
||||
commissionRate, commissionAmount, now, withdrawableAt,
|
||||
"一级代理分成");
|
||||
commissionCount += count;
|
||||
totalCommission = totalCommission.add(commissionAmount);
|
||||
|
||||
log.info("[TenantCommissionCalculateJob] 内购记录 {}, 一级代理 {}, 分成金额 {}",
|
||||
record.getId(), inviterTenantId, commissionAmount);
|
||||
}
|
||||
|
||||
// 6. 创建余额交易记录
|
||||
String bizNo = BizNoGenerator.generate("COMM");
|
||||
TenantBalanceTransactionDO transaction = TenantBalanceTransactionDO.builder()
|
||||
.bizNo(bizNo)
|
||||
.points(commissionAmount)
|
||||
.balance(balance.getBalance())
|
||||
.tenantId(tenantId)
|
||||
.type(COMMISSION_TYPE)
|
||||
.description("邀请用户内购分成")
|
||||
.orderId(record.getTransactionId())
|
||||
.createdAt(now)
|
||||
.remark("内购记录ID: " + record.getId() + ", 被邀请用户: " + record.getUserId())
|
||||
.build();
|
||||
balanceTransactionMapper.insert(transaction);
|
||||
|
||||
// 更新分成记录的关联交易ID
|
||||
commission.setBalanceTransactionId(transaction.getId());
|
||||
commissionMapper.insert(commission);
|
||||
|
||||
commissionCount++;
|
||||
totalCommission = totalCommission.add(commissionAmount);
|
||||
|
||||
log.info("[TenantCommissionCalculateJob] 处理内购记录 {}, 租户 {}, 分成金额 {}",
|
||||
record.getId(), tenantId, commissionAmount);
|
||||
}
|
||||
|
||||
String result = String.format("处理内购记录 %d 条,生成分成 %d 条,总分成金额 %s",
|
||||
@@ -212,4 +224,83 @@ public class TenantCommissionCalculateJob implements JobHandler {
|
||||
log.info("[TenantCommissionCalculateJob] 任务执行完成: {}", result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建分成记录并更新租户余额
|
||||
*
|
||||
* @return 创建的分成记录数量
|
||||
*/
|
||||
private int createCommissionRecord(KeyboardUserPurchaseRecordsDO record,
|
||||
KeyboardUserInvitesDO invite,
|
||||
Long tenantId,
|
||||
BigDecimal purchaseAmount,
|
||||
BigDecimal commissionRate,
|
||||
BigDecimal commissionAmount,
|
||||
LocalDateTime now,
|
||||
LocalDateTime withdrawableAt,
|
||||
String remark) {
|
||||
// 0. 检查该租户是否已对该内购记录计算过分成(防止重复计算)
|
||||
KeyboardTenantCommissionDO existingCommission = commissionMapper.selectByPurchaseRecordIdAndTenantId(record.getId(), tenantId);
|
||||
if (existingCommission != null) {
|
||||
log.debug("[TenantCommissionCalculateJob] 租户 {} 已对内购记录 {} 计算过分成,跳过", tenantId, record.getId());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 1. 创建分成记录
|
||||
KeyboardTenantCommissionDO commission = KeyboardTenantCommissionDO.builder()
|
||||
.purchaseRecordId(record.getId())
|
||||
.transactionId(record.getTransactionId())
|
||||
.inviteeUserId(record.getUserId())
|
||||
.inviterUserId(invite.getProfitEmployeeId())
|
||||
.tenantId(tenantId)
|
||||
.purchaseAmount(purchaseAmount)
|
||||
.commissionRate(commissionRate)
|
||||
.commissionAmount(commissionAmount)
|
||||
.status("SETTLED")
|
||||
.purchaseTime(record.getPurchaseTime())
|
||||
.settledAt(now)
|
||||
.withdrawableAt(withdrawableAt)
|
||||
.withdrawableProcessed(false)
|
||||
.createdAt(now)
|
||||
.updatedAt(now)
|
||||
.remark(remark)
|
||||
.build();
|
||||
|
||||
// 2. 更新租户余额(分成金额先计入总余额,30天后才可提现)
|
||||
TenantBalanceDO balance = tenantBalanceMapper.selectById(tenantId);
|
||||
if (balance == null) {
|
||||
balance = new TenantBalanceDO();
|
||||
balance.setId(tenantId);
|
||||
balance.setBalance(commissionAmount);
|
||||
balance.setWithdrawableBalance(BigDecimal.ZERO);
|
||||
balance.setFrozenAmt(BigDecimal.ZERO);
|
||||
balance.setVersion(0);
|
||||
tenantBalanceMapper.insert(balance);
|
||||
} else {
|
||||
BigDecimal newBalance = balance.getBalance().add(commissionAmount);
|
||||
balance.setBalance(newBalance);
|
||||
tenantBalanceMapper.updateById(balance);
|
||||
}
|
||||
|
||||
// 3. 创建余额交易记录
|
||||
String bizNo = BizNoGenerator.generate("COMM");
|
||||
TenantBalanceTransactionDO transaction = TenantBalanceTransactionDO.builder()
|
||||
.bizNo(bizNo)
|
||||
.points(commissionAmount)
|
||||
.balance(balance.getBalance())
|
||||
.tenantId(tenantId)
|
||||
.type(COMMISSION_TYPE)
|
||||
.description("邀请用户内购分成")
|
||||
.orderId(record.getTransactionId())
|
||||
.createdAt(now)
|
||||
.remark("内购记录ID: " + record.getId() + ", 被邀请用户: " + record.getUserId() + ", " + remark)
|
||||
.build();
|
||||
balanceTransactionMapper.insert(transaction);
|
||||
|
||||
// 4. 更新分成记录的关联交易ID并保存
|
||||
commission.setBalanceTransactionId(transaction.getId());
|
||||
commissionMapper.insert(commission);
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,4 +59,12 @@ public interface KeyboardTenantCommissionService {
|
||||
*/
|
||||
PageResult<KeyboardTenantCommissionDO> getTenantCommissionPage(KeyboardTenantCommissionPageReqVO pageReqVO);
|
||||
|
||||
/**
|
||||
* 获得当前登录租户的分成记录分页
|
||||
*
|
||||
* @param pageReqVO 分页查询
|
||||
* @return 租户内购分成记录分页
|
||||
*/
|
||||
PageResult<KeyboardTenantCommissionDO> getMyTenantCommissionPage(KeyboardTenantCommissionPageReqVO pageReqVO);
|
||||
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import com.yolo.keyboard.framework.common.pojo.PageParam;
|
||||
import com.yolo.keyboard.framework.common.util.object.BeanUtils;
|
||||
|
||||
import com.yolo.keyboard.dal.mysql.tenantcommission.KeyboardTenantCommissionMapper;
|
||||
import com.yolo.keyboard.framework.tenant.core.context.TenantContextHolder;
|
||||
|
||||
import static com.yolo.keyboard.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.yolo.keyboard.framework.common.util.collection.CollectionUtils.convertList;
|
||||
@@ -81,4 +82,10 @@ public class KeyboardTenantCommissionServiceImpl implements KeyboardTenantCommis
|
||||
return tenantCommissionMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<KeyboardTenantCommissionDO> getMyTenantCommissionPage(KeyboardTenantCommissionPageReqVO pageReqVO) {
|
||||
Long tenantId = TenantContextHolder.getRequiredTenantId();
|
||||
return tenantCommissionMapper.selectPageByTenantId(pageReqVO, tenantId);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user