feat(system): 新增套餐消费与余额校验逻辑

- 为 TenantAgencyPackage 增加 validTenantPackage 校验方法
- 在 TenantBalanceService 中实现基于套餐的消费接口
- 新增租户余额消费失败错误码 TENANT_BALANCE_CONSUMPTION_OPERATION_ERROR
- 将积分记录 orderId 类型由 Long 改为 String 以兼容业务流水号
- 消费时自动生成 CONSUMPTION/ORDER 业务编号并记录积分变动
This commit is contained in:
2025-11-24 16:46:15 +08:00
parent 016b4df3e8
commit 0d57c0a5f8
10 changed files with 80 additions and 19 deletions

View File

@@ -34,7 +34,7 @@ public class TenantPointsPageRespVO {
@Schema(description = "订单 Id/业务单号", example = "84")
@ExcelProperty("订单 Id/业务单号")
private Long orderId;
private String orderId;
@Schema(description = "业务流水号(转账、订单等唯一标识)")
@ExcelProperty("业务流水号(转账、订单等唯一标识)")

View File

@@ -34,7 +34,7 @@ public class TenantPointsRespVO {
@Schema(description = "订单 Id/业务单号", example = "84")
@ExcelProperty("订单 Id/业务单号")
private Long orderId;
private String orderId;
@Schema(description = "业务流水号(转账、订单等唯一标识)")
@ExcelProperty("业务流水号(转账、订单等唯一标识)")

View File

@@ -28,7 +28,7 @@ public class TenantPointsSaveReqVO {
private String description;
@Schema(description = "订单 Id/业务单号", example = "84")
private Long orderId;
private String orderId;
@Schema(description = "业务流水号(转账、订单等唯一标识)")
private String bizNo;

View File

@@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.system.dal.dataobject.tenantagencypackage;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
import lombok.*;
import java.util.*;
import java.time.LocalDateTime;
@@ -42,7 +43,8 @@ public class TenantAgencyPackageDO extends BaseDO {
/**
* 关联的菜单编号
*/
private String menuIds;
@TableField(typeHandler = JacksonTypeHandler.class)
private Set<Long> menuIds;
/**
* 套餐天数
*/

View File

@@ -44,7 +44,7 @@ public class TenantPointsDO {
/**
* 订单 Id/业务单号
*/
private Long orderId;
private String orderId;
/**
* 业务流水号(转账、订单等唯一标识)
*/

View File

@@ -134,7 +134,7 @@ public interface ErrorCodeConstants {
ErrorCode TENANT_BALANCE_TRANSFER_PASSWORD_ERROR = new ErrorCode(1_003_017_010, "转账密码错误");
ErrorCode TENANT_BALANCE_TRANSFER_PASSWORD_ERROR_IS_NULL = new ErrorCode(1_003_017_011, "转账密码不能为空");
ErrorCode TENANT_BALANCE_TRANSFER_ERROR_TARGET_NOT_SUBORDINATE = new ErrorCode(1_003_017_012, "转账目标租户不是当前租户的下级");
ErrorCode TENANT_BALANCE_CONSUMPTION_OPERATION_ERROR = new ErrorCode(1_003_017_013, "租户余额消费操作失败");
// ================= 租户套餐 1-003-018-000 ==================
ErrorCode TENANT_AGENCY_PACKAGE_NOT_EXISTS = new ErrorCode(1_003_018_000, "代理租户套餐不存在");

View File

@@ -2,6 +2,8 @@ package cn.iocoder.yudao.module.system.service.tenantagencypackage;
import java.util.*;
import javax.validation.*;
import javax.validation.constraints.NotNull;
import cn.iocoder.yudao.module.system.controller.admin.tenantagencypackage.vo.*;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.system.dal.dataobject.tenantagencypackage.TenantAgencyPackageDO;
@@ -58,4 +60,5 @@ public interface TenantAgencyPackageService {
*/
PageResult<TenantAgencyPackageDO> getTenantAgencyPackagePage(TenantAgencyPackagePageReqVO pageReqVO);
TenantAgencyPackageDO validTenantPackage(@NotNull(message = "租户套餐编号不能为空") Long packageId);
}

View File

@@ -1,6 +1,8 @@
package cn.iocoder.yudao.module.system.service.tenantagencypackage;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenantagencypackage.TenantAgencyPackageDO;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@@ -85,4 +87,17 @@ public class TenantAgencyPackageServiceImpl implements TenantAgencyPackageServic
return tenantAgencyPackageMapper.selectPage(pageReqVO);
}
@Override
public TenantAgencyPackageDO validTenantPackage(Long packageId) {
TenantAgencyPackageDO tenantPackage = tenantAgencyPackageMapper.selectById(packageId);
if (tenantPackage == null) {
throw exception(TENANT_PACKAGE_NOT_EXISTS);
}
if (tenantPackage.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) {
throw exception(TENANT_PACKAGE_DISABLE, tenantPackage.getName());
}
return tenantPackage;
}
}

View File

@@ -67,5 +67,5 @@ public interface TenantBalanceService {
PageResult<TenantBalanceRespVO> getSelfSubordinateTenantBalancePage(@Valid TenantBalancePageReqVO pageReqVO);
Boolean consumption(@Valid TenantBalanceConsumptionReqVO consumptionReqVO);
Boolean consumption(Long PackageId, Long targetTenantId,String remark);
}

View File

@@ -8,10 +8,12 @@ import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.system.controller.admin.tenantbalance.vo.*;
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenantagencypackage.TenantAgencyPackageDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenantbalance.TenantBalanceDO;
import cn.iocoder.yudao.module.system.dal.dataobject.tenantpoints.TenantPointsDO;
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantMapper;
import cn.iocoder.yudao.module.system.dal.mysql.tenantagencypackage.TenantAgencyPackageMapper;
import cn.iocoder.yudao.module.system.dal.mysql.tenantbalance.TenantBalanceMapper;
import cn.iocoder.yudao.module.system.dal.mysql.tenantpoints.TenantPointsMapper;
import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
@@ -54,6 +56,9 @@ public class TenantBalanceServiceImpl implements TenantBalanceService {
@Resource
private TenantMapper tenantMapper;
@Resource
private TenantAgencyPackageMapper tenantAgencyPackageMapper;
@Override
public Long createTenantBalance(TenantBalanceSaveReqVO createReqVO) {
// 插入
@@ -295,20 +300,56 @@ public class TenantBalanceServiceImpl implements TenantBalanceService {
}
@Override
public Boolean consumption(TenantBalanceConsumptionReqVO consumptionReqVO) {
public Boolean consumption(Long PackageId, Long targetTenantId,String remark) {
Long tenantId = TenantContextHolder.getTenantId();
// TenantBalanceDO tenantBalance = tenantBalanceMapper.selectById(tenantId);
// if (tenantBalance == null) {
// throw exception(TENANT_WALLET_NOT_EXISTS);
// }
// if (tenantBalance.getBalance() < consumptionReqVO.getAmount()) {
// throw exception(TENANT_WALLET_NOT_ENOUGH);
// }
// Integer updateCount = tenantBalanceMapper.updateBalanceWithVersion(tenantId, -consumptionReqVO.getAmount(), tenantBalance.getVersion());
// if (updateCount == 0) {
// throw exception(TENANT_BALANCE_CONSUMPTION_OPERATION_ERROR);
// }
Long loginUserId = SecurityFrameworkUtils.getLoginUserId();
String consumption = BizNoGenerator.generate("CONSUMPTION");
String order = BizNoGenerator.generate("ORDER");
// 从数据库中查询租户余额
TenantBalanceDO tenantBalance = tenantBalanceMapper.selectById(tenantId);
if (tenantBalance == null) {
throw exception(TENANT_WALLET_NOT_EXISTS);
}
// 从数据库中查询套餐信息
TenantAgencyPackageDO tenantAgencyPackageDO = tenantAgencyPackageMapper.selectById(PackageId);
if (tenantAgencyPackageDO == null) {
throw exception(TENANT_AGENCY_PACKAGE_NOT_EXISTS);
}
// 检查租户余额是否足够
if (tenantBalance.getBalance() < tenantAgencyPackageDO.getPrice()) {
throw exception(TENANT_BALANCE_BALANCE_NOT_ENOUGH);
}
Integer tenantBalanceDOVersion = tenantBalance.getVersion();
Integer oldBalance = tenantBalance.getBalance();
// 更新租户余额
Integer updateCount = tenantBalanceMapper.updateBalanceWithVersion(tenantId, -tenantAgencyPackageDO.getPrice(), tenantBalanceDOVersion);
if (updateCount == 0) {
throw exception(TENANT_BALANCE_CONSUMPTION_OPERATION_ERROR);
}
// 消费积分变动记录
TenantPointsDO tenantPointsDO = new TenantPointsDO();
tenantPointsDO.setTenantId(tenantId); // 设置租户ID
tenantPointsDO.setPoints(-tenantAgencyPackageDO.getPrice()); // 设置变动积分(负数表示减少)
tenantPointsDO.setBalance(oldBalance - tenantAgencyPackageDO.getPrice()); // 设置变动后的余额
tenantPointsDO.setOperatorId(loginUserId); // 设置操作人ID
tenantPointsDO.setType(CONSUMPTION.getDesc()); // 设置交易类型为转账
tenantPointsDO.setDescription("给租户" + targetTenantId + "开通套餐" + tenantAgencyPackageDO.getName() + ",金额:" + tenantAgencyPackageDO.getPrice()); // 设置交易描述
tenantPointsDO.setTargetTenantId(targetTenantId); // 设置目标租户ID
tenantPointsDO.setBizNo(consumption); // 设置业务流水号
tenantPointsDO.setRemark(remark);
tenantPointsDO.setOrderId(order);
int tenantInsert = tenantPointsMapper.insert(tenantPointsDO); // 插入记录
if (tenantInsert == 0) {
throw exception(TENANT_BALANCE_CONSUMPTION_OPERATION_ERROR);
}
return true;