artisan('lottery:admin-auth-sync')->assertExitCode(0); }); test('agent dashboard returns agent overview for operator with dashboard permission', function (): void { $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id'); $rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id'); $service = app(\App\Services\Agent\AgentNodeService::class); $super = AdminUser::query()->create([ 'username' => 'super_dash_agent', 'name' => 'Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $branch = $service->createChild($super, [ 'parent_id' => $rootId, 'code' => 'dash-branch', 'name' => 'Dash Branch', 'can_create_player' => true, ]); $operator = AdminUser::query()->where('username', 'agent_'.$branch->code)->first(); if ($operator === null) { $operator = AdminUser::query() ->whereIn('id', DB::table('admin_user_agents')->where('agent_node_id', $branch->id)->pluck('admin_user_id')) ->first(); } expect($operator)->not->toBeNull(); $platformRoleId = AgentPlatformRole::id(); $boundRoleId = (int) DB::table('admin_user_agent_roles') ->where('admin_user_id', $operator->id) ->where('agent_node_id', $branch->id) ->value('role_id'); expect($boundRoleId)->toBe($platformRoleId); $slugs = DB::table('admin_role_menu_actions as rma') ->join('admin_menu_actions as ma', 'ma.id', '=', 'rma.menu_action_id') ->where('rma.role_id', $platformRoleId) ->pluck('ma.permission_code') ->all(); expect(in_array('dashboard.view', $slugs, true))->toBeTrue(); $token = $operator->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/dashboard') ->assertOk() ->assertJsonPath('data.agent_overview.agent_node_id', $branch->id) ->assertJsonPath('data.agent_overview.agent_code', 'dash-branch'); }); test('agent dashboard profit uses share profit not team house gross', function (): void { $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id'); $siteCode = (string) DB::table('admin_sites')->where('id', $siteId)->value('code'); $rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id'); $service = app(\App\Services\Agent\AgentNodeService::class); $super = AdminUser::query()->create([ 'username' => 'super_dash_share', 'name' => 'Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $branch = $service->createChild($super, [ 'parent_id' => $rootId, 'code' => 'dash-share-branch', 'name' => 'Dash Share Branch', 'can_create_player' => true, ]); $operator = AdminUser::query()->where('username', 'agent_'.$branch->code)->first(); expect($operator)->not->toBeNull(); $player = Player::query()->create([ 'site_code' => $siteCode, 'site_player_id' => 'native:dash-share-player', 'funding_mode' => PlayerFundingMode::CREDIT, 'username' => 'dash_share_player', 'default_currency' => 'NPR', 'status' => 0, 'agent_node_id' => $branch->id, ]); $draw = \App\Models\Draw::query()->create([ 'draw_no' => 'DRAW-DASH-SHARE', 'business_date' => now()->toDateString(), 'sequence_no' => random_int(1, 9999), 'status' => \App\Lottery\DrawStatus::Open->value, 'current_result_version' => 0, 'settle_version' => 0, 'is_reopened' => false, ]); $orderId = (int) DB::table('ticket_orders')->insertGetId([ 'order_no' => 'ORD-DASH-SHARE', 'player_id' => $player->id, 'draw_id' => $draw->id, 'currency_code' => 'NPR', 'total_bet_amount' => 10_000, 'total_rebate_amount' => 0, 'total_actual_deduct' => 10_000, 'total_estimated_payout' => 0, 'status' => 'confirmed', 'submit_source' => 'h5', 'client_trace_id' => null, 'created_at' => now(), 'updated_at' => now(), ]); $ticketItemId = (int) DB::table('ticket_items')->insertGetId([ 'ticket_no' => 'T-DASH-SHARE', 'order_id' => $orderId, 'player_id' => $player->id, 'draw_id' => $draw->id, 'original_number' => null, 'normalized_number' => '1234', 'play_code' => 'big', 'dimension' => 2, 'digit_slot' => null, 'bet_mode' => null, 'unit_bet_amount' => 10_000, 'total_bet_amount' => 10_000, 'rebate_rate_snapshot' => 0, 'commission_rate_snapshot' => 0, 'actual_deduct_amount' => 10_000, 'odds_snapshot_json' => null, 'rule_snapshot_json' => null, 'combination_count' => 1, 'estimated_max_payout' => 0, 'risk_locked_amount' => 0, 'status' => 'settled_lose', 'win_amount' => 0, 'jackpot_win_amount' => 0, 'created_at' => now(), 'updated_at' => now(), ]); $settledAt = now()->toDateTimeString(); DB::table('share_ledger')->insert([ 'ticket_item_id' => $ticketItemId, 'player_id' => $player->id, 'agent_node_id' => $branch->id, 'agent_path' => json_encode([$branch->id]), 'share_snapshot' => json_encode([ 'total_shares' => [(string) $branch->code => 30.0], 'chain_codes' => [(string) $branch->code], ]), 'game_win_loss' => 1_000, 'basic_rebate' => 0, 'shared_net_win_loss' => 1_000, 'allocations_json' => json_encode([ (string) $branch->code => 300, 'platform' => 700, ]), 'settled_at' => $settledAt, 'created_at' => $settledAt, 'updated_at' => $settledAt, ]); $token = $operator->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/dashboard') ->assertOk() ->assertJsonPath('data.agent_overview.profit_scope', 'share_profit') ->assertJsonPath('data.agent_overview.today_profit_minor', 300); }); test('agent bound admin cannot open platform pnl settlement report', function (): void { $siteId = (int) DB::table('admin_sites')->where('is_default', true)->value('id'); $rootId = (int) DB::table('agent_nodes')->where('admin_site_id', $siteId)->where('depth', 0)->value('id'); $service = app(\App\Services\Agent\AgentNodeService::class); $super = AdminUser::query()->create([ 'username' => 'super_report_block', 'name' => 'Super', 'email' => null, 'password' => Hash::make('secret-strong'), 'status' => 0, ]); grantSuperAdminRole($super); $branch = $service->createChild($super, [ 'parent_id' => $rootId, 'code' => 'report-block-branch', 'name' => 'Report Block Branch', ]); $operator = AdminUser::query()->where('username', 'agent_'.$branch->code)->first(); expect($operator)->not->toBeNull(); $periodId = (int) DB::table('settlement_periods')->insertGetId([ 'admin_site_id' => $siteId, 'period_start' => now()->subDay()->toDateString(), 'period_end' => now()->addDay()->toDateString(), 'status' => 'open', 'created_at' => now(), 'updated_at' => now(), ]); $token = $operator->createToken('test', ['*'], now()->addDay())->plainTextToken; $this->withHeader('Authorization', 'Bearer '.$token) ->getJson('/api/v1/admin/settlement-reports?type=platform_pnl&settlement_period_id='.$periodId) ->assertForbidden(); });