This commit is contained in:
2026-01-12 16:43:08 +08:00
parent dc747139c9
commit 3c6e2683f5
110 changed files with 9630 additions and 481 deletions

View File

@@ -630,18 +630,18 @@ func (s *AgentService) getRebateConfigFloat(ctx context.Context, configKey strin
// giveRebate 发放返佣
func (s *AgentService) giveRebate(ctx context.Context, session sqlx.Session, agentId, sourceAgentId, orderId, productId string, amount float64, levelBonus int64, rebateType int64) error {
// 1. 创建返佣记录
rebate := &model.AgentRebate{
Id: uuid.NewString(),
AgentId: agentId,
SourceAgentId: sourceAgentId,
OrderId: orderId,
ProductId: productId,
RebateType: rebateType,
LevelBonus: float64(levelBonus), // 等级加成金额
RebateAmount: amount,
Status: 1, // 已发放
}
// 1. 创建返佣记录
rebate := &model.AgentRebate{
Id: uuid.NewString(),
AgentId: agentId,
SourceAgentId: sourceAgentId,
OrderId: orderId,
ProductId: productId,
RebateType: rebateType,
LevelBonus: float64(levelBonus), // 等级加成金额
RebateAmount: amount,
Status: 1, // 已发放
}
if _, err := s.AgentRebateModel.Insert(ctx, session, rebate); err != nil {
return errors.Wrapf(err, "创建返佣记录失败")
}
@@ -971,12 +971,12 @@ func (s *AgentService) reconnectToGrandparent(ctx context.Context, session sqlx.
}
// 创建新的关系连接到原上级的上级
relation := &model.AgentRelation{
Id: uuid.NewString(),
ParentId: grandparent.Id,
ChildId: agentId,
RelationType: 1, // 直接关系
}
relation := &model.AgentRelation{
Id: uuid.NewString(),
ParentId: grandparent.Id,
ChildId: agentId,
RelationType: 1, // 直接关系
}
if _, err := s.AgentRelationModel.Insert(ctx, session, relation); err != nil {
return errors.Wrapf(err, "创建新关系失败")
}
@@ -1073,3 +1073,201 @@ func (s *AgentService) GetUpgradeRebate(ctx context.Context, fromLevel, toLevel
}
return 0, nil
}
// CancelAgentCommission 撤销代理佣金和返佣(订单退款时调用)
// 功能:收回已发放的代理佣金和返佣,处理冻结任务
// 余额不足时允许余额为负数(欠款),通过提现限制控制
func (s *AgentService) CancelAgentCommission(ctx context.Context, orderId string) error {
// 1. 查找代理订单
agentOrder, err := s.AgentOrderModel.FindOneByOrderId(ctx, orderId)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
// 不是代理订单,直接返回
return nil
}
return errors.Wrapf(err, "查询代理订单失败, orderId: %s", orderId)
}
// 2. 检查是否已处理(只有已处理的订单才需要收回)
if agentOrder.ProcessStatus != 1 {
logx.Infof("代理订单未处理,无需撤销佣金, orderId: %s, processStatus: %d", orderId, agentOrder.ProcessStatus)
return nil
}
// 3. 查找所有佣金记录
builder := s.AgentCommissionModel.SelectBuilder().
Where("order_id = ? AND del_state = ?", orderId, globalkey.DelStateNo)
commissions, err := s.AgentCommissionModel.FindAll(ctx, builder, "")
if err != nil {
return errors.Wrapf(err, "查询佣金记录失败, orderId: %s", orderId)
}
// 4. 查找所有返佣记录
rebateBuilder := s.AgentRebateModel.SelectBuilder().
Where("order_id = ? AND del_state = ?", orderId, globalkey.DelStateNo)
rebates, err := s.AgentRebateModel.FindAll(ctx, rebateBuilder, "")
if err != nil {
return errors.Wrapf(err, "查询返佣记录失败, orderId: %s", orderId)
}
// 5. 查找相关冻结任务
freezeBuilder := s.AgentFreezeTaskModel.SelectBuilder().
Where("order_id = ? AND del_state = ?", orderId, globalkey.DelStateNo)
freezeTasks, err := s.AgentFreezeTaskModel.FindAll(ctx, freezeBuilder, "")
if err != nil {
return errors.Wrapf(err, "查询冻结任务失败, orderId: %s", orderId)
}
// 6. 使用事务处理收回逻辑
return s.AgentWalletModel.Trans(ctx, func(transCtx context.Context, session sqlx.Session) error {
// 6.1 收回代理佣金
for _, commission := range commissions {
// 只处理已发放的佣金Status=1
if commission.Status != 1 {
logx.Infof("佣金记录状态不是已发放,跳过, commissionId: %s, status: %d", commission.Id, commission.Status)
continue
}
// 查找冻结任务(如果有)
var freezeTask *model.AgentFreezeTask
for _, task := range freezeTasks {
if task.CommissionId == commission.Id && task.Status == 1 {
freezeTask = task
break
}
}
// 扣除代理钱包余额
if err := s.deductCommissionFromWallet(transCtx, session, commission.AgentId, commission.Amount, freezeTask); err != nil {
return errors.Wrapf(err, "扣除代理佣金失败, agentId: %s, amount: %.2f", commission.AgentId, commission.Amount)
}
// 更新佣金记录状态为已撤销
commission.Status = 3 // 3=已取消
if err := s.AgentCommissionModel.UpdateWithVersion(transCtx, session, commission); err != nil {
return errors.Wrapf(err, "更新佣金记录状态失败, commissionId: %s", commission.Id)
}
// 如果有冻结任务,更新为已撤销
if freezeTask != nil {
freezeTask.Status = 3 // 3=已取消
if err := s.AgentFreezeTaskModel.UpdateWithVersion(transCtx, session, freezeTask); err != nil {
return errors.Wrapf(err, "更新冻结任务状态失败, freezeTaskId: %s", freezeTask.Id)
}
}
}
// 6.2 收回返佣
for _, rebate := range rebates {
// 只处理已发放的返佣Status=1
if rebate.Status != 1 {
logx.Infof("返佣记录状态不是已发放,跳过, rebateId: %s, status: %d", rebate.Id, rebate.Status)
continue
}
// 扣除上级代理钱包余额
if err := s.deductRebateFromWallet(transCtx, session, rebate.AgentId, rebate.RebateAmount); err != nil {
return errors.Wrapf(err, "扣除返佣失败, agentId: %s, amount: %.2f", rebate.AgentId, rebate.RebateAmount)
}
// 更新返佣记录状态为已撤销
rebate.Status = 3 // 3=已取消
if err := s.AgentRebateModel.UpdateWithVersion(transCtx, session, rebate); err != nil {
return errors.Wrapf(err, "更新返佣记录状态失败, rebateId: %s", rebate.Id)
}
}
// 6.3 更新代理订单备注(可选,记录撤销信息)
agentOrder.ProcessRemark = sql.NullString{
String: "订单退款,佣金和返佣已撤销",
Valid: true,
}
if err := s.AgentOrderModel.UpdateWithVersion(transCtx, session, agentOrder); err != nil {
logx.Errorf("更新代理订单备注失败, orderId: %s, err: %v", orderId, err)
// 不阻断流程,只记录日志
}
return nil
})
}
// deductCommissionFromWallet 从钱包扣除佣金(支持余额不足,允许负数)
func (s *AgentService) deductCommissionFromWallet(ctx context.Context, session sqlx.Session, agentId string, amount float64, freezeTask *model.AgentFreezeTask) error {
wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, agentId)
if err != nil {
return errors.Wrapf(err, "查询钱包失败, agentId: %s", agentId)
}
// 计算可用余额
availableBalance := wallet.Balance + wallet.FrozenBalance
// 如果有冻结任务,需要先解冻
if freezeTask != nil && freezeTask.Status == 1 {
freezeAmount := freezeTask.FreezeAmount
// 解冻:从冻结余额转回可用余额
if wallet.FrozenBalance >= freezeAmount {
wallet.FrozenBalance -= freezeAmount
wallet.Balance += freezeAmount
} else {
// 冻结余额不足(异常情况),记录日志
logx.Errorf("冻结余额不足,异常情况, agentId: %s, frozenBalance: %.2f, freezeAmount: %.2f", agentId, wallet.FrozenBalance, freezeAmount)
wallet.Balance += wallet.FrozenBalance
wallet.FrozenBalance = 0
}
}
// 扣除佣金金额
if availableBalance >= amount {
// 余额充足:优先扣除冻结余额,再扣除可用余额
if wallet.FrozenBalance >= amount {
wallet.FrozenBalance -= amount
} else {
remaining := amount - wallet.FrozenBalance
wallet.FrozenBalance = 0
wallet.Balance -= remaining
}
} else {
// 余额不足:扣除所有可用余额,允许负数
wallet.Balance = wallet.Balance - (amount - wallet.FrozenBalance)
wallet.FrozenBalance = 0
// 记录日志:欠款金额
logx.Errorf("代理余额不足,产生欠款: agentId=%s, orderId=%s, debt=%.2f, balance=%.2f",
agentId, "", -wallet.Balance, wallet.Balance)
}
// TotalEarnings 始终减少(即使余额为负数)
wallet.TotalEarnings -= amount
if wallet.TotalEarnings < 0 {
wallet.TotalEarnings = 0 // 累计收益不能为负数
}
return s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet)
}
// deductRebateFromWallet 从钱包扣除返佣(支持余额不足,允许负数)
func (s *AgentService) deductRebateFromWallet(ctx context.Context, session sqlx.Session, agentId string, amount float64) error {
wallet, err := s.AgentWalletModel.FindOneByAgentId(ctx, agentId)
if err != nil {
return errors.Wrapf(err, "查询钱包失败, agentId: %s", agentId)
}
// 扣除返佣金额
if wallet.Balance >= amount {
// 余额充足:正常扣除
wallet.Balance -= amount
} else {
// 余额不足:扣除所有余额,允许负数
wallet.Balance -= amount
// 记录日志:欠款金额
logx.Errorf("代理返佣余额不足,产生欠款: agentId=%s, debt=%.2f, balance=%.2f",
agentId, -wallet.Balance, wallet.Balance)
}
// TotalEarnings 始终减少(即使余额为负数)
wallet.TotalEarnings -= amount
if wallet.TotalEarnings < 0 {
wallet.TotalEarnings = 0 // 累计收益不能为负数
}
return s.AgentWalletModel.UpdateWithVersion(ctx, session, wallet)
}