Browse Source

修复:累计充值达标后会员等级不升级

- 人工充值也计入经验并触发升级,不再限制 from=1
- calculateLevel 按数值排序,避免 recharge 字符串字典序导致等级偏低
- 去掉本次充值金额重复累加
- 新增 users:sync-levels 命令修复已卡住的用户等级

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
doge 5 hours ago
parent
commit
2d0f42d443
2 changed files with 59 additions and 13 deletions
  1. 30 0
      app/Console/Commands/SyncUserLevels.php
  2. 29 13
      app/Services/BalanceLogService.php

+ 30 - 0
app/Console/Commands/SyncUserLevels.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Console\Commands;
+
+use App\Models\User;
+use App\Services\BalanceLogService;
+use Illuminate\Console\Command;
+
+class SyncUserLevels extends Command
+{
+    protected $signature = 'users:sync-levels {--member= : Only this member_id}';
+    protected $description = 'Raise users.level to match cumulative recharge (经验); never lowers a level';
+
+    public function handle(): int
+    {
+        $query = User::query()->select('member_id')->orderBy('id');
+        if ($member = $this->option('member')) $query->where('member_id', $member);
+        $changed = 0;
+        $query->chunk(500, function ($users) use (&$changed) {
+            foreach ($users as $user) {
+                [$before, $after] = BalanceLogService::syncLevel($user->member_id);
+                if ($after === $before) continue;
+                $changed++;
+                $this->line("{$user->member_id}: {$before} -> {$after}");
+            }
+        });
+        $this->info("updated {$changed} users");
+        return self::SUCCESS;
+    }
+}

+ 29 - 13
app/Services/BalanceLogService.php

@@ -20,6 +20,8 @@ class BalanceLogService extends BaseService
     public static string $MODEL = BalanceLog::class;
     //后台充值可选择的类型
     public static array $manualRecharge = ['人工充值', '注册赠送', '优惠活动'];
+    //计入累计充值(等级经验)的类型
+    public static array $levelRechargeTypes = ['充值', '人工充值', '三方充值'];
     //需要计算回水的类型
     public static array $computeBackFlowChangeTypes = ['充值', '人工充值', '三方充值', '提现', '人工扣款', '三方提现'];
     //全部类型
@@ -190,18 +192,16 @@ class BalanceLogService extends BaseService
         if ($room_id) $data['room_id'] = $room_id;
         $result = static::$MODEL::create($data);
 
+        //累计充值(经验)变化,人工充值同样计入,所有用户都要更新等级
+        if (in_array($change_type, self::$levelRechargeTypes, true)) {
+            self::syncLevel($memberId);
+        }
+
         //充值返现活动仅限前台用户充值,后台人工充值不触发活动赠送
         if ( in_array($change_type, ['充值','三方充值'])) {
             $user = User::where('member_id', $memberId)->first();
             if ($user->from == 1) {
-                //充值更新用户等级
-                $total_recharge = self::getTotalRecharge($memberId);
-                $total_recharge = bcadd($total_recharge, $amount, 2);
-                $level = self::calculateLevel($total_recharge);
-                if ($level > $user->level) {
-                    $user->level = $level;
-                    $user->save();
-                }
+                $level = self::calculateLevel(self::getTotalRecharge($memberId));
 
                 $remark_amount = bcadd($amount, 0, 2);
                 //返现比例(给邀请人返现)
@@ -302,7 +302,21 @@ class BalanceLogService extends BaseService
      */
     public static function getTotalRecharge($memberId): float
     {
-        return static::$MODEL::where('member_id', $memberId)->where('type',1)->whereIn('change_type', ['充值','人工充值', '三方充值'])->sum('amount');
+        return static::$MODEL::where('member_id', $memberId)->where('type',1)->whereIn('change_type', self::$levelRechargeTypes)->sum('amount');
+    }
+
+    /**
+     * 按累计充值重算用户等级,只升不降(保留后台手动调高的等级)
+     * @return array{0:int,1:int} [原等级, 新等级]
+     */
+    public static function syncLevel($memberId): array
+    {
+        $user = User::where('member_id', $memberId)->first(['id', 'level']);
+        if (!$user) return [0, 0];
+        $before = (int)$user->level;
+        $level = self::calculateLevel(self::getTotalRecharge($memberId));
+        if ($level > $before) User::where('id', $user->id)->where('level', '<', $level)->update(['level' => $level]);
+        return [$before, max($before, $level)];
     }
 
     /**
@@ -312,13 +326,15 @@ class BalanceLogService extends BaseService
      */
     public static function calculateLevel(float $totalAmount): int
     {
-        $levelList = Level::orderBy('recharge', 'desc')->get()->toArray();
+        // 按数值排序,避免 recharge 字段为字符串时按字典序排错(如 '500' 排在 '10000' 前面)
+        $levelList = Level::get(['level', 'recharge'])->sort(fn ($a, $b) => bccomp((string)$b->recharge, (string)$a->recharge, 2));
+        $total = number_format($totalAmount, 2, '.', '');
         foreach ($levelList as $item) {
-            if ($totalAmount >= $item['recharge']) {
-                return $item['level'];
+            if (bccomp($total, (string)$item->recharge, 2) >= 0) {
+                return (int)$item->level;
             }
         }
-        return 0; 
+        return 0;
     }
 
     /**