doge 2 өдөр өмнө
parent
commit
9b4588a2f6

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

@@ -252,37 +252,29 @@ class AgentReportService
         $total = ['bet_count' => 0, 'bet_amount' => '0.0000', 'valid_bet_amount' => '0.0000', 'win_loss' => '0.0000'];
         $sources = [];
         if (Schema::hasTable('bets')) {
-            $sources[] = DB::table('bets')->whereIn('member_id', clone $memberIds)->where('status', 2)
-                ->whereBetween('created_at', [$start, $end])
-                ->selectRaw('COUNT(*) bet_count, COALESCE(SUM(amount),0) bet_amount, COALESCE(SUM(amount),0) valid_bet_amount, COALESCE(SUM(profit - amount),0) win_loss')->first();
+            $orders = DB::table('bets')->where('status', 2)->whereBetween('created_at', [$start, $end]);
+            $sources[] = $this->scopedBetTotals($orders, 'member_id', clone $memberIds,
+                'COUNT(*) bet_count, SUM(amount) bet_amount, SUM(amount) valid_bet_amount, SUM(profit - amount) win_loss', $platform);
         }
         foreach (['sport_game_order', 'jisu_game_order'] as $table) {
             if (!Schema::hasTable($table)) {
                 continue;
             }
-            $sources[] = DB::table($table)->whereIn('member_id', clone $memberIds)->whereIn('status', [1, 2])
-                ->whereBetween('created_at', [$start, $end])
-                ->selectRaw('COUNT(*) bet_count, COALESCE(SUM(amount),0) bet_amount, COALESCE(SUM(amount),0) valid_bet_amount, COALESCE(SUM(CASE WHEN status = 1 THEN -amount ELSE profit_and_loss END),0) win_loss')->first();
+            $orders = DB::table($table)->whereIn('status', [1, 2])->whereBetween('created_at', [$start, $end]);
+            $sources[] = $this->scopedBetTotals($orders, 'member_id', clone $memberIds,
+                'COUNT(*) bet_count, SUM(amount) bet_amount, SUM(amount) valid_bet_amount, SUM(CASE WHEN status = 1 THEN -amount ELSE profit_and_loss END) win_loss', $platform);
         }
         if (Schema::hasTable('lhc_order')) {
             $amountExpression = Schema::hasColumn('lhc_order', 'total_amount') ? 'COALESCE(total_amount, amount)' : 'amount';
             $winLossExpression = '(COALESCE(win_amount, 0) - ' . $amountExpression . ')';
-            $sources[] = DB::table('lhc_order')->whereIn('member_id', clone $memberIds)->whereIn('lottery_status', [1, 2])
-                ->whereBetween('created_at', [$start->timestamp, $end->timestamp])
-                ->selectRaw("COUNT(*) bet_count, COALESCE(SUM({$amountExpression}),0) bet_amount, COALESCE(SUM({$amountExpression}),0) valid_bet_amount, COALESCE(SUM({$winLossExpression}),0) win_loss")->first();
+            $orders = DB::table('lhc_order')->whereIn('lottery_status', [1, 2])->whereBetween('created_at', [$start->timestamp, $end->timestamp]);
+            $sources[] = $this->scopedBetTotals($orders, 'member_id', clone $memberIds,
+                "COUNT(*) bet_count, SUM({$amountExpression}) bet_amount, SUM({$amountExpression}) valid_bet_amount, SUM({$winLossExpression}) win_loss", $platform);
         }
         if (Schema::hasTable('third_game_orders')) {
             $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();
-            }
+            $sources[] = $this->scopedBetTotals($orders, 'user_id', (clone $members)->select('id'),
+                'COUNT(*) bet_count, SUM(bet_amount) bet_amount, SUM(valid_amount) valid_bet_amount, SUM(settled_amount) win_loss', $platform);
         }
         foreach ($sources as $row) {
             $total['bet_count'] += (int) ($row->bet_count ?? 0);
@@ -293,6 +285,22 @@ class AgentReportService
         return $total;
     }
 
+    private function scopedBetTotals($orders, string $identity, $eligible, string $totals, bool $platform): object
+    {
+        if (!$platform) {
+            return $orders->whereIn($identity, $eligible)->selectRaw($totals)->first();
+        }
+
+        // 全平台先汇总同一会员的注单,避免每笔注单都查询会员表。
+        $byMember = $orders->whereNotNull($identity)->select($identity)->selectRaw($totals)->groupBy($identity);
+        if ($identity === 'member_id' && $orders->getConnection()->getDriverName() === 'mysql') {
+            // 旧注单表的字符排序规则可能不同,保留不同字节的会员号,仍按原规则匹配会员表。
+            $byMember->groupByRaw('CAST(member_id AS BINARY)');
+        }
+        return DB::query()->fromSub($byMember, 'game_member_totals')->whereIn($identity, $eligible)
+            ->selectRaw('SUM(bet_count) bet_count, SUM(bet_amount) bet_amount, SUM(valid_bet_amount) valid_bet_amount, SUM(win_loss) win_loss')->first();
+    }
+
     private function emptySummary(): array
     {
         return [

+ 39 - 0
database/migrations/2026_11_11_180000_add_agent_dashboard_legacy_bet_indexes.php

@@ -0,0 +1,39 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    private const INDEXES = [
+        ['bets', 'agent_dashboard_bets_date', ['status', 'created_at', 'member_id', 'amount', 'profit']],
+        ['sport_game_order', 'agent_dashboard_sport_date', ['status', 'created_at', 'member_id', 'amount', 'profit_and_loss']],
+        ['jisu_game_order', 'agent_dashboard_jisu_date', ['status', 'created_at', 'member_id', 'amount', 'profit_and_loss']],
+        ['lhc_order', 'agent_dashboard_lhc_date', ['lottery_status', 'created_at', 'member_id', 'amount', 'win_amount']],
+    ];
+
+    public function up(): void
+    {
+        foreach (self::INDEXES as [$table, $name, $columns]) {
+            if (!Schema::hasTable($table) || !Schema::hasColumns($table, $columns)) {
+                continue;
+            }
+            if ($table === 'lhc_order' && Schema::hasColumn($table, 'total_amount')) {
+                $columns[] = 'total_amount';
+            }
+            if (!Schema::hasIndex($table, $columns)) {
+                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));
+            }
+        }
+    }
+};

+ 49 - 0
tests/Integration/AgentDashboardTest.php

@@ -147,6 +147,55 @@ class AgentDashboardTest extends TestCase
         $this->assertSame(array_intersect_key($platform, $fundFields), array_intersect_key($own, $fundFields));
     }
 
+    public function test_yearly_shareholder_dashboard_preserves_legacy_order_formulas_and_scope(): void
+    {
+        Schema::table('users', fn (Blueprint $table) => $table->integer('from')->default(1));
+        foreach (['bets' => 'profit', 'sport_game_order' => 'profit_and_loss', 'jisu_game_order' => 'profit_and_loss'] as $source => $winColumn) {
+            Schema::create($source, function (Blueprint $table) use ($winColumn) {
+                $table->string('member_id');
+                $table->integer('status');
+                $table->dateTime('created_at');
+                $table->decimal('amount', 18, 4);
+                $table->decimal($winColumn, 18, 4)->nullable();
+            });
+        }
+        Schema::create('lhc_order', function (Blueprint $table) {
+            $table->string('member_id');
+            $table->integer('lottery_status');
+            $table->unsignedBigInteger('created_at');
+            $table->decimal('amount', 18, 4);
+            $table->decimal('total_amount', 18, 4)->nullable();
+            $table->decimal('win_amount', 18, 4)->nullable();
+        });
+        $joint = $this->agent();
+        $equity = $this->agent(Agent::TYPE_EQUITY);
+        foreach ([['own', $joint->id, 1], ['direct', null, 1], ['guest', null, 2]] as [$member, $agentId, $from]) {
+            DB::table('users')->insert(['member_id' => $member, 'username' => $member, 'agent_id' => $agentId, 'from' => $from]);
+        }
+        $at = '2025-09-08 00:00:00';
+        foreach ([
+            ['bets', 'own', 2, 10, 'profit', 14],
+            ['bets', 'own', 1, 999, 'profit', 999],
+            ['sport_game_order', 'own', 1, 20, 'profit_and_loss', 999],
+            ['jisu_game_order', 'direct', 2, 30, 'profit_and_loss', 9],
+            ['jisu_game_order', 'guest', 2, 999, 'profit_and_loss', 999],
+            ['jisu_game_order', 'orphan', 2, 999, 'profit_and_loss', 999],
+        ] as [$source, $member, $status, $amount, $winColumn, $win]) {
+            DB::table($source)->insert(['member_id' => $member, 'status' => $status, 'created_at' => $at, 'amount' => $amount, $winColumn => $win]);
+        }
+        foreach ([['own', 2, 40, 50, 55], ['direct', 1, 60, null, null], ['direct', 0, 999, 999, 999]] as [$member, $status, $amount, $total, $win]) {
+            DB::table('lhc_order')->insert(['member_id' => $member, 'lottery_status' => $status, 'created_at' => strtotime($at),
+                'amount' => $amount, 'total_amount' => $total, 'win_amount' => $win]);
+        }
+
+        $reports = new AgentReportService();
+        $fields = array_flip(['bet_count', 'bet_amount', 'valid_bet_amount', 'win_loss']);
+        $platform = $reports->dashboard($equity, '2025-09-08', '2026-09-07');
+        $own = $reports->dashboard($joint, '2025-09-08', '2026-09-07');
+        $this->assertSame([5, '170.0000', '170.0000', '-62.0000'], array_values(array_intersect_key($platform, $fields)));
+        $this->assertSame([3, '80.0000', '80.0000', '-11.0000'], array_values(array_intersect_key($own, $fields)));
+    }
+
     public function test_charts_group_registrations_and_deduplicate_successful_daily_deposits_across_channels(): void
     {
         $agent = $this->agent();