diff --git a/keyboard-server/src/main/java/com/yolo/keyboard/api/tenantbalance/TenantBalanceApiImpl.java b/keyboard-server/src/main/java/com/yolo/keyboard/api/tenantbalance/TenantBalanceApiImpl.java new file mode 100644 index 0000000..57e49de --- /dev/null +++ b/keyboard-server/src/main/java/com/yolo/keyboard/api/tenantbalance/TenantBalanceApiImpl.java @@ -0,0 +1,44 @@ +package com.yolo.keyboard.api.tenantbalance; + +import com.yolo.keyboard.dal.dataobject.tenantbalance.TenantBalanceDO; +import com.yolo.keyboard.dal.mysql.tenantbalance.TenantBalanceMapper; +import com.yolo.keyboard.module.system.api.tenantbalance.TenantBalanceApi; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.math.BigDecimal; + +/** + * 租户余额 API 实现类 + * + * @author ziin + */ +@Service +@Slf4j +public class TenantBalanceApiImpl implements TenantBalanceApi { + + @Resource + private TenantBalanceMapper tenantBalanceMapper; + + @Override + public void initTenantBalance(Long tenantId) { + // 检查是否已存在 + TenantBalanceDO existingBalance = tenantBalanceMapper.selectById(tenantId); + if (existingBalance != null) { + log.info("[initTenantBalance] 租户 {} 钱包已存在,跳过初始化", tenantId); + return; + } + + // 初始化租户钱包 + TenantBalanceDO balance = new TenantBalanceDO(); + balance.setId(tenantId); + balance.setBalance(BigDecimal.ZERO); + balance.setWithdrawableBalance(BigDecimal.ZERO); + balance.setFrozenAmt(BigDecimal.ZERO); + balance.setVersion(0); + tenantBalanceMapper.insert(balance); + + log.info("[initTenantBalance] 租户 {} 钱包初始化成功", tenantId); + } +} diff --git a/keyboard-server/src/main/java/com/yolo/keyboard/dal/dataobject/tenantbalance/TenantBalanceDO.java b/keyboard-server/src/main/java/com/yolo/keyboard/dal/dataobject/tenantbalance/TenantBalanceDO.java index 9512b71..088c510 100644 --- a/keyboard-server/src/main/java/com/yolo/keyboard/dal/dataobject/tenantbalance/TenantBalanceDO.java +++ b/keyboard-server/src/main/java/com/yolo/keyboard/dal/dataobject/tenantbalance/TenantBalanceDO.java @@ -15,7 +15,7 @@ import com.yolo.keyboard.framework.mybatis.core.dataobject.BaseDO; * @author 芋道源码 */ @TableName("system_tenant_balance") -@KeySequence("system_tenant_balance_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 +//@KeySequence("system_tenant_balance_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) diff --git a/keyboard-server/src/main/java/com/yolo/keyboard/dal/dataobject/tenantcommission/KeyboardTenantCommissionDO.java b/keyboard-server/src/main/java/com/yolo/keyboard/dal/dataobject/tenantcommission/KeyboardTenantCommissionDO.java index e51be3d..c0e476f 100644 --- a/keyboard-server/src/main/java/com/yolo/keyboard/dal/dataobject/tenantcommission/KeyboardTenantCommissionDO.java +++ b/keyboard-server/src/main/java/com/yolo/keyboard/dal/dataobject/tenantcommission/KeyboardTenantCommissionDO.java @@ -100,6 +100,16 @@ public class KeyboardTenantCommissionDO { */ private LocalDateTime updatedAt; + /** + * 可提现时间(结算时间 + 30天) + */ + private LocalDateTime withdrawableAt; + + /** + * 是否已处理转可提现:false-未处理,true-已处理 + */ + private Boolean withdrawableProcessed; + /** * 备注 */ diff --git a/keyboard-server/src/main/java/com/yolo/keyboard/job/CommissionWithdrawableJob.java b/keyboard-server/src/main/java/com/yolo/keyboard/job/CommissionWithdrawableJob.java new file mode 100644 index 0000000..2fc5437 --- /dev/null +++ b/keyboard-server/src/main/java/com/yolo/keyboard/job/CommissionWithdrawableJob.java @@ -0,0 +1,132 @@ +package com.yolo.keyboard.job; + +import cn.hutool.core.collection.CollUtil; +import com.yolo.keyboard.dal.dataobject.tenantbalance.TenantBalanceDO; +import com.yolo.keyboard.dal.dataobject.tenantbalancetransaction.TenantBalanceTransactionDO; +import com.yolo.keyboard.dal.dataobject.tenantcommission.KeyboardTenantCommissionDO; +import com.yolo.keyboard.dal.mysql.tenantbalance.TenantBalanceMapper; +import com.yolo.keyboard.dal.mysql.tenantbalancetransaction.TenantBalanceTransactionMapper; +import com.yolo.keyboard.dal.mysql.tenantcommission.KeyboardTenantCommissionMapper; +import com.yolo.keyboard.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.yolo.keyboard.framework.quartz.core.handler.JobHandler; +import com.yolo.keyboard.framework.tenant.core.aop.TenantIgnore; +import com.yolo.keyboard.utils.BizNoGenerator; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 分成可提现转换定时任务 + * 每小时执行一次,将满30天的分成金额转为可提现余额 + * + * @author ziin + */ +@Component +@Slf4j +public class CommissionWithdrawableJob implements JobHandler { + + @Resource + private KeyboardTenantCommissionMapper commissionMapper; + + @Resource + private TenantBalanceMapper tenantBalanceMapper; + + @Resource + private TenantBalanceTransactionMapper balanceTransactionMapper; + + private static final String WITHDRAWABLE_TYPE = "WITHDRAWABLE"; + + @Override + @TenantIgnore + @Transactional(rollbackFor = Exception.class) + public String execute(String param) { + log.info("[CommissionWithdrawableJob] 开始执行分成可提现转换任务"); + + LocalDateTime now = LocalDateTime.now(); + + // 1. 查询已到可提现时间且未处理的分成记录 + List commissions = commissionMapper.selectList( + new LambdaQueryWrapperX() + .le(KeyboardTenantCommissionDO::getWithdrawableAt, now) + .eq(KeyboardTenantCommissionDO::getWithdrawableProcessed, false) + .eq(KeyboardTenantCommissionDO::getStatus, "SETTLED") + ); + + if (CollUtil.isEmpty(commissions)) { + log.info("[CommissionWithdrawableJob] 没有需要处理的分成记录"); + return "没有需要处理的分成记录"; + } + + // 2. 按租户分组汇总金额 + Map> tenantCommissionsMap = commissions.stream() + .collect(Collectors.groupingBy(KeyboardTenantCommissionDO::getTenantId)); + + int tenantCount = 0; + int commissionCount = 0; + BigDecimal totalAmount = BigDecimal.ZERO; + + // 3. 逐个租户处理 + for (Map.Entry> entry : tenantCommissionsMap.entrySet()) { + Long tenantId = entry.getKey(); + List tenantCommissions = entry.getValue(); + + // 计算该租户的总可提现金额 + BigDecimal tenantTotalAmount = tenantCommissions.stream() + .map(KeyboardTenantCommissionDO::getCommissionAmount) + .reduce(BigDecimal.ZERO, BigDecimal::add); + + // 更新租户可提现余额 + TenantBalanceDO balance = tenantBalanceMapper.selectById(tenantId); + if (balance == null) { + log.warn("[CommissionWithdrawableJob] 租户 {} 余额记录不存在,跳过", tenantId); + continue; + } + + BigDecimal currentWithdrawable = balance.getWithdrawableBalance() != null + ? balance.getWithdrawableBalance() : BigDecimal.ZERO; + BigDecimal newWithdrawable = currentWithdrawable.add(tenantTotalAmount); + balance.setWithdrawableBalance(newWithdrawable); + tenantBalanceMapper.updateById(balance); + + // 创建余额交易记录 + String bizNo = BizNoGenerator.generate("WDB"); + TenantBalanceTransactionDO transaction = TenantBalanceTransactionDO.builder() + .bizNo(bizNo) + .points(tenantTotalAmount) + .balance(balance.getBalance()) + .tenantId(tenantId) + .type(WITHDRAWABLE_TYPE) + .description("分成转可提现") + .createdAt(now) + .remark("共 " + tenantCommissions.size() + " 笔分成转为可提现") + .build(); + balanceTransactionMapper.insert(transaction); + + // 标记分成记录为已处理 + for (KeyboardTenantCommissionDO commission : tenantCommissions) { + commission.setWithdrawableProcessed(true); + commission.setUpdatedAt(now); + commissionMapper.updateById(commission); + } + + tenantCount++; + commissionCount += tenantCommissions.size(); + totalAmount = totalAmount.add(tenantTotalAmount); + + log.info("[CommissionWithdrawableJob] 处理租户 {},分成 {} 笔,金额 {}", + tenantId, tenantCommissions.size(), tenantTotalAmount); + } + + String result = String.format("处理租户 %d 个,分成记录 %d 条,总金额 %s", + tenantCount, commissionCount, totalAmount.toPlainString()); + log.info("[CommissionWithdrawableJob] 任务执行完成: {}", result); + return result; + } +} diff --git a/keyboard-server/src/main/java/com/yolo/keyboard/job/TenantCommissionCalculateJob.java b/keyboard-server/src/main/java/com/yolo/keyboard/job/TenantCommissionCalculateJob.java index e0f02c7..29319a8 100644 --- a/keyboard-server/src/main/java/com/yolo/keyboard/job/TenantCommissionCalculateJob.java +++ b/keyboard-server/src/main/java/com/yolo/keyboard/job/TenantCommissionCalculateJob.java @@ -145,6 +145,7 @@ public class TenantCommissionCalculateJob implements JobHandler { // 4. 创建分成记录 LocalDateTime now = LocalDateTime.now(); + LocalDateTime withdrawableAt = now.plusDays(30); // 30天后可提现 KeyboardTenantCommissionDO commission = KeyboardTenantCommissionDO.builder() .purchaseRecordId(record.getId()) .transactionId(record.getTransactionId()) @@ -157,17 +158,20 @@ public class TenantCommissionCalculateJob implements JobHandler { .status("SETTLED") .purchaseTime(record.getPurchaseTime()) .settledAt(now) + .withdrawableAt(withdrawableAt) + .withdrawableProcessed(false) .createdAt(now) .updatedAt(now) .build(); - // 5. 更新租户余额 + // 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); diff --git a/yolo-module-system/src/main/java/com/yolo/keyboard/module/system/api/tenantbalance/TenantBalanceApi.java b/yolo-module-system/src/main/java/com/yolo/keyboard/module/system/api/tenantbalance/TenantBalanceApi.java new file mode 100644 index 0000000..e7b9626 --- /dev/null +++ b/yolo-module-system/src/main/java/com/yolo/keyboard/module/system/api/tenantbalance/TenantBalanceApi.java @@ -0,0 +1,17 @@ +package com.yolo.keyboard.module.system.api.tenantbalance; + +/** + * 租户余额 API 接口 + * + * @author ziin + */ +public interface TenantBalanceApi { + + /** + * 初始化租户钱包 + * + * @param tenantId 租户ID + */ + void initTenantBalance(Long tenantId); + +} diff --git a/yolo-module-system/src/main/java/com/yolo/keyboard/module/system/service/tenant/TenantServiceImpl.java b/yolo-module-system/src/main/java/com/yolo/keyboard/module/system/service/tenant/TenantServiceImpl.java index 311ce47..776c00e 100644 --- a/yolo-module-system/src/main/java/com/yolo/keyboard/module/system/service/tenant/TenantServiceImpl.java +++ b/yolo-module-system/src/main/java/com/yolo/keyboard/module/system/service/tenant/TenantServiceImpl.java @@ -22,6 +22,7 @@ import com.yolo.keyboard.module.system.dal.dataobject.tenant.TenantDO; import com.yolo.keyboard.module.system.dal.dataobject.tenant.TenantPackageDO; import com.yolo.keyboard.module.system.dal.mysql.tenant.TenantMapper; import com.yolo.keyboard.module.system.api.invitecode.UserInviteCodeApi; +import com.yolo.keyboard.module.system.api.tenantbalance.TenantBalanceApi; import com.yolo.keyboard.module.system.enums.permission.RoleCodeEnum; import com.yolo.keyboard.module.system.enums.permission.RoleTypeEnum; import com.yolo.keyboard.module.system.service.permission.MenuService; @@ -79,6 +80,9 @@ public class TenantServiceImpl implements TenantService { @Autowired(required = false) private UserInviteCodeApi userInviteCodeApi; + @Autowired(required = false) + private TenantBalanceApi tenantBalanceApi; + @Override public List getTenantIdList() { List tenants = tenantMapper.selectList(); @@ -143,6 +147,10 @@ public class TenantServiceImpl implements TenantService { if ("代理".equals(createReqVO.getTenantType()) && userInviteCodeApi != null) { userInviteCodeApi.createInviteCodeForAgent(userId, tenant.getId()); } + // 为代理租户初始化钱包 + if ("代理".equals(createReqVO.getTenantType()) && tenantBalanceApi != null) { + tenantBalanceApi.initTenantBalance(tenant.getId()); + } }); return tenant.getId(); }