doge 2 дней назад
Родитель
Сommit
6ec29b08ba

+ 19 - 7
app/Services/Agent/AgentReportService.php

@@ -30,7 +30,7 @@ class AgentReportService
         if ($agent->isEquity()) {
             // 全平台:排除试玩账号和测试会员(测试代理名下)
             $members = StatsScope::realUsers(User::query(), 'users');
-            return $this->aggregate($members, 0, [], $startDate, $endDate) + ['scope' => 'platform'];
+            return $this->aggregate($members, 0, [], $startDate, $endDate, platform: true) + ['scope' => 'platform'];
         }
         return $this->summaryForTree($agent, $startDate, $endDate) + ['scope' => 'own'];
     }
@@ -107,7 +107,7 @@ class AgentReportService
         return $this->aggregate(User::query()->whereIn('member_id', $memberIds), 0, [], $startDate, $endDate);
     }
 
-    private function aggregate($members, int $agentCount, array $commissionAgentIds, string $startDate, string $endDate): array
+    private function aggregate($members, int $agentCount, array $commissionAgentIds, string $startDate, string $endDate, bool $platform = false): array
     {
         $start = Carbon::parse($startDate)->startOfDay();
         $end = Carbon::parse($endDate)->endOfDay();
@@ -137,6 +137,10 @@ class AgentReportService
 
         $funds = DB::table('balance_logs')->whereIn('member_id', clone $memberIds)
             ->whereBetween('created_at', [$start, $end])
+            ->whereIn('change_type', [
+                '人工充值', '订单补偿', '人工扣款', '注册赠送', '优惠活动', '即充即送',
+                '充值返现', '老用户回归', '比比返', '笔笔返', '返水', '回水',
+            ])
             ->selectRaw(<<<'SQL'
                 COALESCE(SUM(CASE WHEN ((change_type = '人工充值' AND related_id IS NULL) OR change_type = '订单补偿') AND amount > 0 THEN amount ELSE 0 END), 0) AS manual_credit,
                 COALESCE(SUM(CASE WHEN change_type = '人工扣款' AND amount < 0 THEN ABS(amount) ELSE 0 END), 0) AS manual_debit,
@@ -144,7 +148,7 @@ class AgentReportService
                 COALESCE(SUM(CASE WHEN change_type IN ('比比返','笔笔返','返水','回水') AND amount > 0 THEN amount ELSE 0 END), 0) AS rebate_amount
             SQL)->first();
 
-        $bet = $this->betSummary($members, $memberIds, $start, $end);
+        $bet = $this->betSummary($members, $memberIds, $start, $end, $platform);
         $companyProfit = $this->companyProfit(
             (string) $depositAmount,
             (string) $withdrawAmount,
@@ -243,7 +247,7 @@ class AgentReportService
         return compact('total', 'page', 'limit', 'list');
     }
 
-    private function betSummary($members, $memberIds, Carbon $start, Carbon $end): array
+    private function betSummary($members, $memberIds, Carbon $start, Carbon $end, bool $platform): array
     {
         $total = ['bet_count' => 0, 'bet_amount' => '0.0000', 'valid_bet_amount' => '0.0000', 'win_loss' => '0.0000'];
         $sources = [];
@@ -268,9 +272,17 @@ class AgentReportService
                 ->selectRaw("COUNT(*) bet_count, COALESCE(SUM({$amountExpression}),0) bet_amount, COALESCE(SUM({$amountExpression}),0) valid_bet_amount, COALESCE(SUM({$winLossExpression}),0) win_loss")->first();
         }
         if (Schema::hasTable('third_game_orders')) {
-            $sources[] = DB::table('third_game_orders')->whereIn('user_id', (clone $members)->select('id'))->where('status', 1)
-                ->whereBetween('last_update_time', [$start, $end])
-                ->selectRaw('COUNT(*) bet_count, COALESCE(SUM(bet_amount),0) bet_amount, COALESCE(SUM(valid_amount),0) valid_bet_amount, COALESCE(SUM(settled_amount),0) win_loss')->first();
+            $orders = DB::table('third_game_orders')->where('status', 1)->whereBetween('last_update_time', [$start, $end]);
+            $userIds = (clone $members)->select('id');
+            $totals = 'COUNT(*) bet_count, SUM(bet_amount) bet_amount, SUM(valid_amount) valid_bet_amount, SUM(settled_amount) win_loss';
+            if ($platform) {
+                // 先合并同一会员的注单,全平台查询只需按会员判断一次统计范围。
+                $byUser = $orders->whereNotNull('user_id')->select('user_id')->selectRaw($totals)->groupBy('user_id');
+                $sources[] = DB::query()->fromSub($byUser, 'third_game_user_totals')->whereIn('user_id', $userIds)
+                    ->selectRaw('SUM(bet_count) bet_count, SUM(bet_amount) bet_amount, SUM(valid_bet_amount) valid_bet_amount, SUM(win_loss) win_loss')->first();
+            } else {
+                $sources[] = $orders->whereIn('user_id', $userIds)->selectRaw($totals)->first();
+            }
         }
         foreach ($sources as $row) {
             $total['bet_count'] += (int) ($row->bet_count ?? 0);

+ 33 - 0
database/migrations/2026_11_11_170000_add_agent_dashboard_report_indexes.php

@@ -0,0 +1,33 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    private const INDEXES = [
+        ['balance_logs', 'agent_dashboard_balance_type_date', ['change_type', 'created_at', 'related_id', 'member_id', 'amount']],
+        ['balance_logs', 'agent_dashboard_balance_member_date', ['member_id', 'created_at', 'change_type', 'related_id', 'amount']],
+        ['third_game_orders', 'agent_dashboard_third_settled_date', ['status', 'last_update_time', 'user_id', 'bet_amount', 'valid_amount', 'settled_amount']],
+    ];
+
+    public function up(): void
+    {
+        foreach (self::INDEXES as [$table, $name, $columns]) {
+            if (!Schema::hasTable($table) || !Schema::hasColumns($table, $columns) || Schema::hasIndex($table, $columns)) {
+                continue;
+            }
+            Schema::table($table, fn (Blueprint $blueprint) => $blueprint->index($columns, $name));
+        }
+    }
+
+    public function down(): void
+    {
+        foreach (self::INDEXES as [$table, $name]) {
+            if (Schema::hasTable($table) && Schema::hasIndex($table, $name)) {
+                Schema::table($table, fn (Blueprint $blueprint) => $blueprint->dropIndex($name));
+            }
+        }
+    }
+};

+ 61 - 0
tests/Integration/AgentDashboardTest.php

@@ -86,6 +86,67 @@ class AgentDashboardTest extends TestCase
         $this->assertSame(0, $rows[$equity->id]['member_count'], 'admin agent report still counts only the equity agent\'s own members');
     }
 
+    public function test_yearly_dashboard_preserves_third_game_date_boundaries_and_finance_totals(): void
+    {
+        Schema::table('users', function (Blueprint $table) {
+            $table->integer('from')->default(1);
+            $table->integer('user_type')->default(0);
+        });
+        Schema::create('third_game_orders', function (Blueprint $table) {
+            $table->id();
+            $table->unsignedBigInteger('user_id')->nullable();
+            $table->integer('status');
+            $table->dateTime('last_update_time');
+            foreach (['bet_amount', 'valid_amount', 'settled_amount'] as $field) {
+                $table->decimal($field, 18, 4)->nullable();
+            }
+        });
+        $joint = $this->agent();
+        $equity = $this->agent(Agent::TYPE_EQUITY);
+        $test = $this->agents->create(['username' => 'dashboard-test', 'password' => 'secret1', 'is_test' => 1]);
+        $users = [];
+        foreach ([['own', $joint->id, 1, 0], ['direct', null, 1, 0], ['guest', null, 2, 1], ['simulation', null, 1, 2], ['test', $test->id, 1, 0]] as [$member, $agentId, $from, $userType]) {
+            $users[$member] = DB::table('users')->insertGetId([
+                'member_id' => $member, 'username' => $member, 'agent_id' => $agentId, 'from' => $from, 'user_type' => $userType,
+            ]);
+        }
+
+        $from = '2025-09-08';
+        $to = '2026-09-07';
+        foreach ([
+            [$users['own'], 1, "$from 00:00:00", 10, 8, -2],
+            [$users['own'], 1, "$to 23:59:59", 20, null, 5],
+            [$users['direct'], 1, "$to 12:00:00", 40, 35, 7],
+            [$users['own'], 0, "$to 12:00:00", 999, 999, 999],
+            [$users['own'], 1, '2025-09-07 23:59:59', 999, 999, 999],
+            [$users['own'], 1, '2026-09-08 00:00:00', 999, 999, 999],
+            [$users['guest'], 1, "$to 12:00:00", 999, 999, 999],
+            [$users['simulation'], 1, "$to 12:00:00", 999, 999, 999],
+            [$users['test'], 1, "$to 12:00:00", 999, 999, 999],
+            [null, 1, "$to 12:00:00", 999, 999, 999],
+        ] as [$userId, $status, $at, $bet, $valid, $win]) {
+            DB::table('third_game_orders')->insert(['user_id' => $userId, 'status' => $status, 'last_update_time' => $at,
+                'bet_amount' => $bet, 'valid_amount' => $valid, 'settled_amount' => $win]);
+        }
+        foreach ([['人工充值', null, 40], ['订单补偿', null, 2], ['人工扣款', null, -3], ['注册赠送', null, 1], ['优惠活动', null, 2],
+            ['即充即送', null, 3], ['充值返现', null, 4], ['老用户回归', null, 5], ['人工充值', 0, 6], ['人工充值', 123, 999],
+            ['比比返', null, 1], ['笔笔返', null, 2], ['返水', null, 3], ['回水', null, 4], ['结算', null, 999],
+        ] as [$type, $relatedId, $amount]) {
+            DB::table('balance_logs')->insert(['member_id' => 'own', 'change_type' => $type, 'related_id' => $relatedId,
+                'amount' => $amount, 'created_at' => "$from 00:00:00"]);
+        }
+
+        $reports = new AgentReportService();
+        $betFields = array_flip(['bet_count', 'bet_amount', 'valid_bet_amount', 'win_loss']);
+        $fundFields = array_flip(['manual_credit', 'manual_debit', 'bonus_amount', 'rebate_amount']);
+        $platform = $reports->dashboard($equity, $from, $to);
+        $own = $reports->dashboard($joint, $from, $to);
+        $this->assertSame([3, '70.0000', '43.0000', '10.0000'], array_values(array_intersect_key($platform, $betFields)));
+        $this->assertSame([2, '30.0000', '8.0000', '3.0000'], array_values(array_intersect_key($own, $betFields)));
+        $this->assertSame(['42.0000', '3.0000', '21.0000', '10.0000'], array_values(array_intersect_key($platform, $fundFields)));
+        $this->assertSame(array_intersect_key($platform, $fundFields), array_intersect_key($own, $fundFields));
+    }
+
     public function test_charts_group_registrations_and_deduplicate_successful_daily_deposits_across_channels(): void
     {
         $agent = $this->agent();